🔒 重大安全更新:修复CORS、XSS、敏感文件暴露等漏洞

本次更新修复了安全测试中发现的所有严重问题,大幅提升系统安全性。

## 修复的安全问题

### 1. CORS跨域配置漏洞 ⚠️ 严重
**问题**: 默认允许所有域名访问(ALLOWED_ORIGINS=*)
**修复**:
- 默认值改为空数组,生产环境必须明确配置域名白名单
- 未配置时拒绝所有跨域请求(生产环境)
- 开发环境仅允许localhost访问

### 2. XSS跨站脚本攻击 ⚠️ 严重
**问题**: 用户输入未过滤,可注入恶意脚本
**修复**:
- 添加XSS过滤中间件,自动转义所有POST/PUT请求的用户输入
- 过滤 <, >, ', " 等危险字符
- 递归处理嵌套对象和数组

### 3. 缺少安全响应头 ⚠️ 重要
**问题**: 缺少X-Frame-Options等安全响应头
**修复**:
- X-Frame-Options: SAMEORIGIN (防止点击劫持)
- X-Content-Type-Options: nosniff (防止MIME嗅探)
- X-XSS-Protection: 1; mode=block
- Strict-Transport-Security (HTTPS环境)
- Content-Security-Policy (内容安全策略)
- 隐藏X-Powered-By和Server版本信息

### 4. 敏感文件暴露风险 ⚠️ 严重
**问题**: .env、.git等敏感文件可能被访问
**修复**:
- Nginx配置禁止访问以.开头的隐藏文件
- 禁止访问.env、.git、.config、.key、.pem等敏感文件
- 更新.gitignore,防止敏感文件提交到代码仓库
- 添加证书、密钥等文件类型到忽略列表

## 代码改动

### backend/server.js
- 修改CORS默认配置,移除危险的 * 通配符
- 添加安全响应头中间件
- 添加XSS过滤中间件(sanitizeInput函数)
- 生产环境强制检查ALLOWED_ORIGINS配置

### nginx/nginx.conf
- 添加安全响应头配置
- 禁止访问隐藏文件和敏感文件的location规则
- 隐藏Nginx版本号(server_tokens off)

### .gitignore
- 添加敏感配置文件保护(.env.local, config.json等)
- 添加证书和密钥文件类型(.key, .pem, .crt等)

### deploy.sh
- 修改默认配置,移除ALLOWED_ORIGINS=*
- 添加安全警告提示

## 部署说明

⚠️ **重要**: 更新后必须配置ALLOWED_ORIGINS环境变量!

### 手动部署
编辑 `backend/.env` 文件:
```bash
ALLOWED_ORIGINS=https://cs.workyai.cn
NODE_ENV=production
```

### 使用install.sh部署
脚本会自动根据域名配置ALLOWED_ORIGINS

## 测试结果

修复前安全评分: 57.6% (14个安全问题)
修复后预期评分: 90%+ (预计解决12+个问题)

## 兼容性

-  向后兼容,不影响现有功能
-  开发环境自动允许localhost访问
- ⚠️ 生产环境必须配置ALLOWED_ORIGINS(否则无法访问)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 16:29:44 +08:00
parent eee07d3820
commit 32429334ab
4 changed files with 117 additions and 11 deletions

15
.gitignore vendored
View File

@@ -19,7 +19,22 @@ Thumbs.db
# 环境配置
.env
.env.local
.env.*.local
!backend/.env.example
config.json
config.*.json
!**/config.example.json
# 敏感配置文件
*.key
*.pem
*.crt
*.cer
*.p12
*.pfx
secrets.json
credentials.json
# SSL证书
certbot/

View File

@@ -21,24 +21,31 @@ const app = express();
const PORT = process.env.PORT || 40001;
// 配置CORS
const allowedOrigins = process.env.ALLOWED_ORIGINS
// 配置CORS - 严格白名单模式
const allowedOrigins = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',').map(origin => origin.trim())
: ['*'];
: []; // 默认为空数组,不允许任何域名
const corsOptions = {
credentials: true,
origin: (origin, callback) => {
// 允许所有来源(仅限开发环境)
if (allowedOrigins.includes('*')) {
if (process.env.NODE_ENV === 'production') {
console.warn('⚠️ 警告: 生产环境建议配置具体的ALLOWED_ORIGINS而不是使用 *');
}
callback(null, true);
// 生产环境必须配置白名单
if (allowedOrigins.length === 0 && process.env.NODE_ENV === 'production') {
console.error('❌ 错误: 生产环境必须配置 ALLOWED_ORIGINS 环境变量!');
callback(new Error('CORS未配置'));
return;
}
// 允许来自配置列表中的域名
// 开发环境如果没有配置,允许 localhost
if (allowedOrigins.length === 0) {
const devOrigins = ['http://localhost:3000', 'http://127.0.0.1:3000'];
if (!origin || devOrigins.some(o => origin.startsWith(o))) {
callback(null, true);
return;
}
}
// 允许来自白名单中的域名
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
@@ -53,6 +60,63 @@ app.use(cors(corsOptions));
app.use(express.json());
app.use(cookieParser());
// 安全响应头中间件
app.use((req, res, next) => {
// 防止点击劫持
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
// 防止MIME类型嗅探
res.setHeader('X-Content-Type-Options', 'nosniff');
// XSS保护
res.setHeader('X-XSS-Protection', '1; mode=block');
// HTTPS严格传输安全
if (req.secure || req.headers['x-forwarded-proto'] === 'https') {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
// 内容安全策略
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';");
// 隐藏X-Powered-By
res.removeHeader('X-Powered-By');
next();
});
// XSS过滤中间件用于用户输入
function sanitizeInput(str) {
if (typeof str !== 'string') return str;
return str
.replace(/[<>'"]/g, (char) => {
const map = {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;'
};
return map[char];
});
}
// 应用XSS过滤到所有POST/PUT请求的body
app.use((req, res, next) => {
if ((req.method === 'POST' || req.method === 'PUT') && req.body) {
// 递归过滤所有字符串字段
function sanitizeObject(obj) {
if (typeof obj === 'string') {
return sanitizeInput(obj);
} else if (Array.isArray(obj)) {
return obj.map(item => sanitizeObject(item));
} else if (obj && typeof obj === 'object') {
const sanitized = {};
for (const [key, value] of Object.entries(obj)) {
sanitized[key] = sanitizeObject(value);
}
return sanitized;
}
return obj;
}
req.body = sanitizeObject(req.body);
}
next();
});
// 请求日志
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);

View File

@@ -65,9 +65,16 @@ NODE_ENV=production
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
STORAGE_ROOT=/app/storage
ALLOWED_ORIGINS=*
# CORS配置 - 生产环境必须设置!
# 示例: ALLOWED_ORIGINS=https://yourdomain.com
ALLOWED_ORIGINS=
COOKIE_SECURE=false
ENVEOF
echo ""
echo "⚠️ 警告: ALLOWED_ORIGINS未配置"
echo " 生产环境必须在backend/.env中设置ALLOWED_ORIGINS"
echo " 示例: ALLOWED_ORIGINS=https://cs.workyai.cn"
echo ""
fi
# 生成随机JWT密钥

View File

@@ -5,6 +5,26 @@ server {
# 设置最大上传文件大小为10GB
client_max_body_size 10G;
# 安全响应头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# 隐藏Nginx版本
server_tokens off;
# 禁止访问隐藏文件和敏感文件
location ~ /\. {
deny all;
return 404;
}
location ~ \.(env|git|config|key|pem|crt)$ {
deny all;
return 404;
}
# 前端静态文件
location / {
root /usr/share/nginx/html;