name: security-fundamentals description: 审查安全,包括OWASP Top 10、输入验证、认证。当初级开发者构建登录、认证、存储密码、处理用户输入、API密钥、JWT令牌,或询问“这安全吗”时使用。
安全基础审查
“安全不是功能。它是基础。建在沙子上,房子会倒塌。”
何时应用
在审查时激活此技能:
- 认证/登录流程
- 授权检查
- 用户输入处理
- 数据库查询
- 文件上传
- API端点
- 响应中的数据暴露
审查清单
输入验证(绝不信任客户端)
- [ ] 所有输入已验证:每个用户输入在使用前都检查了吗?
- [ ] 服务器端验证:验证在服务器上完成,不仅客户端吗?
- [ ] 类型检查:强制执行预期类型吗?
- [ ] 长度限制:字符串长度有界限吗?
- [ ] 白名单优于黑名单:明确定义允许的值吗?
认证
- [ ] 密码哈希:密码是否哈希(bcrypt、argon2),而不是加密?
- [ ] 无明文秘密:秘密在环境变量中,而不是代码中吗?
- [ ] 令牌过期:JWT/会话有合理的过期时间吗?
- [ ] 安全传输:强制执行HTTPS吗?
授权
- [ ] 所有权检查:用户只能访问他们自己的数据吗?
- [ ] 角色验证:管理员路由受角色检查保护吗?
- [ ] 无客户端认证:授权在服务器端强制执行吗?
数据暴露
- [ ] 最小化响应:API只返回必要字段吗?
- [ ] URL中无敏感数据:令牌/ID不在查询字符串中吗?
- [ ] 日志中无敏感数据:密码/令牌从日志中排除吗?
OWASP Top 10 快速检查
1. 注入(SQL、NoSQL、命令)
❌ db.query(`SELECT * FROM users WHERE id = ${userId}`);
✅ db.query('SELECT * FROM users WHERE id = ?', [userId]);
2. 破损认证
❌ if (req.headers.admin === 'true') { /* 允许管理员 */ }
✅ const user = await verifyToken(req.headers.authorization);
if (user.role !== 'admin') throw new ForbiddenError();
3. 敏感数据暴露
❌ res.json({ user: { ...user, password, ssn } });
✅ res.json({ user: { id: user.id, name: user.name } });
4. 破损访问控制
❌ app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});
✅ app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (user.id !== req.user.id && req.user.role !== 'admin') {
throw new ForbiddenError();
}
res.json(user);
});
5. 安全配置错误
❌ CORS: origin: '*'
❌ 生产环境中的详细错误消息
❌ 生产环境中启用调试模式
✅ CORS: origin: process.env.ALLOWED_ORIGINS
✅ 向客户端发送通用错误消息
✅ 生产环境中禁用调试模式
6. 跨站脚本(XSS)
❌ element.innerHTML = userInput;
✅ element.textContent = userInput;
✅ DOMPurify.sanitize(userInput);
苏格拉底式问题
向初级开发者提问而不是给出答案:
- 信任:“什么阻止恶意用户在这里发送任何他们想要的东西?”
- 所有权:“你怎么知道这个用户拥有这个资源?”
- 暴露:“如果这个端点暴露了,最坏的情况会发生什么?”
- 秘密:“如果我
git clone这个仓库,我会看到什么秘密?” - 注入:“如果有人发送
'; DROP TABLE users; --作为输入怎么办?”
需要指出的危险信号
| 标志 | 风险 | 问题 |
|---|---|---|
| 查询中的字符串拼接 | SQL注入 | “这个输入可以包含SQL吗?” |
eval() 或 new Function() |
代码注入 | “为什么需要动态代码执行?” |
使用用户数据的 innerHTML |
XSS | “如果用户包含 <script> 怎么办?” |
| 日志中的密码 | 数据泄露 | “谁能看到这些日志?” |
| 认证上无限流 | 暴力破解 | “什么阻止某人尝试每个密码?” |
CORS: * |
安全绕过 | “任何网站都应该能够调用这个API吗?” |
| 无过期的JWT | 令牌盗窃 | “如果这个令牌被盗会发生什么?” |
| URL中的ID | IDOR | “用户A可以通过更改ID访问用户B的数据吗?” |
部署前安全清单
- [ ] 所有秘密在环境变量中
- [ ] 强制执行HTTPS
- [ ] 所有端点上的输入验证
- [ ] 防止SQL/NoSQL注入(参数化查询)
- [ ] 防止XSS(输出编码)
- [ ] 启用CSRF保护
- [ ] 认证端点上的速率限制
- [ ] 从响应中排除敏感数据
- [ ] 每个受保护路由上的授权检查
- [ ] 设置安全头部(helmet.js 或等效)
永远不要这样做
| 行动 | 原因 |
|---|---|
| 以明文存储密码 | 一次泄露暴露所有用户 |
| 将秘密放入代码 | Git历史是永久的 |
| 仅信任客户端验证 | 任何人都可以绕过客户端 |
| 返回完整数据库对象 | 暴露内部字段 |
| 记录敏感数据 | 日志也会被泄露 |
使用 md5 或 sha1 作为密码 |
密码学上已破损 |