🔒 安全加固:修复多个中高危漏洞

修复内容:
1. Host Header 注入 - 添加 PUBLIC_BASE_URL 和 ALLOWED_HOSTS 白名单
2. API密钥暴力破解 - 添加速率限制(5次/小时,封锁24小时)
3. 路径遍历漏洞 - 增强路径验证,防止空字节注入和目录遍历
4. 令牌安全 - 密码重置和邮箱验证令牌使用SHA256哈希存储
5. 文件上传安全 - 阻止PHP/JSP/ASP等可执行脚本上传
6. IDOR防护 - 增强权限验证和安全日志
7. XSS防护 - 增强输入过滤,阻止javascript:等危险协议
8. 日志脱敏 - 移除验证码等敏感信息的日志输出
9. CSRF增强 - HTTPS环境使用strict模式Cookie
10. 邮箱枚举防护 - 密码重置统一返回消息
11. 速率限制 - 文件列表(60次/分)和上传(100次/小时)

配置说明:
- PUBLIC_BASE_URL: 必须配置,用于生成安全的邮件链接
- ALLOWED_HOSTS: 可选,Host头白名单
- COOKIE_SECURE=true: 生产环境必须开启

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-25 09:48:46 +08:00
parent 47fe1466a4
commit 474c8fe9b5
3 changed files with 325 additions and 92 deletions

View File

@@ -492,16 +492,20 @@ const SettingsDB = {
}
};
// 邮箱验证管理
// 邮箱验证管理(增强安全:哈希存储)
const VerificationDB = {
setVerification(userId, token, expiresAtMs) {
// 对令牌进行哈希后存储
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
db.prepare(`
UPDATE users
SET verification_token = ?, verification_expires_at = ?, is_verified = 0, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(token, expiresAtMs, userId);
`).run(hashedToken, expiresAtMs, userId);
},
consumeVerificationToken(token) {
// 对用户提供的令牌进行哈希
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
const row = db.prepare(`
SELECT * FROM users
WHERE verification_token = ?
@@ -512,7 +516,7 @@ const VerificationDB = {
OR verification_expires_at > CURRENT_TIMESTAMP -- 兼容旧的字符串时间
)
AND is_verified = 0
`).get(token);
`).get(hashedToken);
if (!row) return null;
db.prepare(`
@@ -524,23 +528,30 @@ const VerificationDB = {
}
};
// 密码重置 Token 管理
// 密码重置 Token 管理(增强安全:哈希存储)
const PasswordResetTokenDB = {
// 创建令牌时存储哈希值
create(userId, token, expiresAtMs) {
// 对令牌进行哈希后存储(防止数据库泄露时令牌被直接使用)
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
db.prepare(`
INSERT INTO password_reset_tokens (user_id, token, expires_at, used)
VALUES (?, ?, ?, 0)
`).run(userId, token, expiresAtMs);
`).run(userId, hashedToken, expiresAtMs);
},
// 验证令牌时先哈希再比较
use(token) {
// 对用户提供的令牌进行哈希
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
const row = db.prepare(`
SELECT * FROM password_reset_tokens
WHERE token = ? AND used = 0 AND (
expires_at > strftime('%s','now')*1000 -- 数值时间戳
OR expires_at > CURRENT_TIMESTAMP -- 兼容旧的字符串时间
)
`).get(token);
`).get(hashedToken);
if (!row) return null;
// 立即标记为已使用(防止重复使用)
db.prepare(`UPDATE password_reset_tokens SET used = 1 WHERE id = ?`).run(row.id);
return row;
}