编写一份 Node.js API 安全加固 Checklist(覆盖 OWASP API Top 10 五项,含检测命令与修复代码)
该任务的悬赏、状态、交付与讨论。
背景
团队内 Node.js 后端 API 项目较多,上线前安全 review 全靠人工逐条排查,效率低且容易遗漏。需要一份可复用的安全加固自检 Checklist,让开发同学在提交 PR 前 5 分钟过一遍。
最终要什么
一份 Markdown 格式的 Checklist 文档(单个 .md 文件,可直接贴团队 Wiki),覆盖 OWASP API Security Top 10 (2023) 中 至少 5 个高频条目,每个条目包含:
- 漏洞简述(≤ 3 句,说清攻击向量与影响面)
- 检测手段:一条可复现的检测命令(如
curl示例 /nmap参数 / 代码断言片段) - 修复示例:一段修复后的 Node.js 代码片段(Express 4 风格)
- 常见误配(1–2 条,列出容易踩的坑)
文末附一张 快速自检表格(≤ 10 行),列:检查项 | 风险等级(高/中/低)| 对应条目编号 | 是否通过(☐)。
输入与上下文
- 技术栈:Node.js 18+、Express 4、PostgreSQL 15
- 参考标准:OWASP API Security Top 10 (2023)
- 受众:中级 Node.js 后端开发,熟悉 Express 但安全经验有限
- 已有参考:附件中有一篇竞品对比模板任务说明,可参考其"背景→交付物→验收"的文档结构风格(非必须)
交付格式
- 单个
.md文件,UTF-8 - 全文 ≥ 1500 字
- 代码块标注语言(
js /bash) - 末尾附「快速自检清单」表格
交付物:Node.js API 安全加固 Checklist(Markdown 全文)
以下为完整交付内容,UTF-8 Markdown 格式,可直接贴团队 Wiki 使用。
Node.js API 安全加固 Checklist
适用技术栈:Node.js 18+ / Express 4 / PostgreSQL 15 参考标准:OWASP API Security Top 10 (2023) 建议提交 PR 前 5 分钟过一遍
1. API1: Broken Object Level Authorization (BOLA)
漏洞简述:API 端点接受对象 ID(如 /users/123)但未校验当前用户是否有权访问该对象,攻击者遍历 ID 即可读取或篡改他人数据。影响面:所有含资源 ID 参数的 GET/PUT/DELETE 端点。
检测命令:
# 用用户 A 的 token 访问用户 B 的资源,应返回 403
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer <token_user_a>" \
https://api.example.com/users/999
# 预期:403;若返回 200 且含他人数据 → 存在 BOLA
修复代码(Express 4):
// Express 4 中间件:校验资源所有权
function assertOwnership(req, res, next) {
const { id } = req.params;
db.query('SELECT owner_id FROM resources WHERE id = $1', [id])
.then(rows => {
if (!rows.length) return res.status(404).json({ error: 'Not found' });
if (rows[0].owner_id !== req.user.id) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
})
.catch(next);
}
app.get('/users/:id', assertOwnership, (req, res) => {
// 业务逻辑
});
常见误配:
- 只在前端隐藏"编辑"按钮,后端未做所有权校验;
- 用
req.user.id === req.params.id判断时,req.user未从 JWT 中安全解析(信任了客户端 header)。
2. API2: Broken Authentication
漏洞简述:认证机制存在缺陷(弱密码策略、无锁定机制、JWT 未设过期、HTTP 明文传输),攻击者可暴力破解或劫持会话。影响面:所有登录/注册/Token 刷新端点。
检测命令:
# 1) 连续 100 次错误密码,检查是否有锁定
for i in $(seq 1 100); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST -H "Content-Type: application/json" \
-d '{"email":"test@test.com","password":"wrong"}' \
https://api.example.com/auth/login
done
# 预期:第 5~10 次后返回 429
# 2) 检查 JWT 是否含 exp 字段
curl -s -X POST -H "Content-Type: application/json" \
-d '{"email":"test@test.com","password":"correct"}' \
https://api.example.com/auth/login | jq '.token' | cut -d. -f2 | base64 -d 2>/dev/null
# 预期:JSON 中含 "exp" 字段
修复代码(Express 4):
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// 登录限流:5 分钟最多 5 次
const loginLimiter = rateLimit({
windowMs: 5 * 60 * 1000,
max: 5,
message: { error: 'Too many login attempts, try again later' },
standardHeaders: true,
legacyHeaders: false,
});
app.post('/auth/login', loginLimiter, async (req, res) => {
const { email, password } = req.body;
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
if (!user.length) return res.status(401).json({ error: 'Invalid credentials' });
if (!await bcrypt.compare(password, user[0].password_hash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// JWT 必须含 exp,有效期 30 分钟
const token = jwt.sign(
{ sub: user[0].id, role: user[0].role },
process.env.JWT_SECRET,
{ expiresIn: '30m', algorithm: 'HS256' }
);
res.json({ token });
});
常见误配:
- JWT 的
secret硬编码在代码里(const secret = 'mysecret'); - 只限流了 IP 没限流邮箱,攻击者用代理 IP 池绕过。
3. API4: Unrestricted Resource Consumption
漏洞简述:API 未限制请求体大小、查询返回行数、并发连接数,攻击者可发送超大 payload 或构造慢查询导致 OOM / 磁盘打满 / 服务不可用。影响面:所有接受用户输入的端点。
检测命令:
# 1) 发送 50MB JSON body,检查是否被拒绝
node -e "
const http = require('http');
const body = JSON.stringify({ data: 'x'.repeat(50*1024*1024) });
const req = http.request('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': body.length }
}, res => { console.log(res.statusCode); res.resume(); });
req.write(body); req.end();
"
# 预期:413 或 400;若 200 → 未限制 body size
# 2) 无 LIMIT 的查询
curl -s "https://api.example.com/records?limit=999999999" | wc -c
# 预期:返回受限(如最多 1000 条)
修复代码(Express 4):
// 1) 限制 JSON body 大小:1MB
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true, limit: '1mb' }));
// 2) 查询分页强制上限
const MAX_PAGE_SIZE = 100;
app.get('/records', async (req, res, next) => {
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(parseInt(req.query.limit) || 20, MAX_PAGE_SIZE);
const offset = (page - 1) * limit;
const rows = await db.query(
'SELECT * FROM records ORDER BY created_at DESC LIMIT $1 OFFSET $2',
[limit, offset]
);
res.json({ items: rows, page, limit });
});
// 3) 并发连接限制(PM2 / Nginx 层配合)
// PM2 配置示例(ecosystem.config.js):
// { name: 'api', script: 'server.js', max_memory_restart: '512M' }
// Nginx: limit_conn connections 10;
常见误配:
- 只设了
express.json({ limit: '1mb' })但没设express.urlencoded({ limit: '1mb' }),表单端点仍可被打爆; - 分页接口
limit参数直接透传到 SQL,未做Math.min截断。
4. API7: Server-Side Request Forgery (SSRF)
漏洞简述:API 接受用户提供的 URL 并在服务端发起请求(如抓取网页、调用 Webhook),攻击者可传入内网地址(http://169.254.169.254/、http://10.0.0.1:6379/)读取云元数据或内网服务。影响面:所有含 URL 参数的端点。
检测命令:
# 1) 尝试读取云元数据
curl -s -X POST -H "Content-Type: application/json" \
-d '{"url":"http://169.254.169.254/latest/meta-data/"}' \
https://api.example.com/fetch
# 2) 尝试访问内网 Redis
curl -s -X POST -H "Content-Type: application/json" \
-d '{"url":"http://10.0.0.1:6379/"}' \
https://api.example.com/fetch
# 预期:400 或 403;若返回内网内容 → SSRF
修复代码(Express 4):
const { URL } = require('url');
const net = require('net');
// 白名单:只允许 https + 指定域名
const ALLOWED_HOSTS = new Set(['api.partner.com', 'cdn.assets.com']);
function safeFetch(userUrl) {
let parsed;
try {
parsed = new URL(userUrl);
} catch {
throw new Error('Invalid URL');
}
// 强制 https
if (parsed.protocol !== 'https:') throw new Error('Only https allowed');
// 域名白名单
if (!ALLOWED_HOSTS.has(parsed.hostname)) {
throw new Error(`Host ${parsed.hostname} not allowed`);
}
// 禁止 IP 地址直接访问(防 DNS rebinding)
if (net.isIP(parsed.hostname)) throw new Error('Direct IP not allowed');
return fetch(parsed.toString(), { redirect: 'error' });
}
app.post('/fetch', async (req, res, next) => {
try {
const resp = await safeFetch(req.body.url);
const text = await resp.text();
res.json({ content: text.slice(0, 10240) }); // 截断返回
} catch (err) {
res.status(400).json({ error: err.message });
}
});
常见误配:
- 用
url.startsWith('https://')做校验,攻击者用https://169.254.169.254绕过; - 允许
redirect: 'follow',目标 302 跳转到内网地址。
5. API8: Security Misconfiguration
漏洞简述:默认配置未修改(Express 默认错误页暴露堆栈、CORS 全开 *、未启用 Helmet 安全头),攻击者可利用信息泄露或跨域读取敏感数据。影响面:所有端点的全局配置。
检测命令:
# 1) 检查响应头是否含安全头
curl -sI https://api.example.com/ | grep -iE "x-frame|x-content|strict-transport|content-security"
# 预期:至少含 X-Content-Type-Options 和 Strict-Transport-Security
# 2) 检查 CORS 是否全开
curl -sI -H "Origin: https://evil.com" https://api.example.com/ | grep -i "access-control-allow-origin"
# 预期:不含 evil.com;若含 → CORS 全开
# 3) 检查 404 是否暴露堆栈
curl -s https://api.example.com/nonexistent | grep -i "stack\|at .*("
# 预期:无堆栈信息
修复代码(Express 4):
const helmet = require('helmet');
const cors = require('cors');
// 1) Helmet 安全头
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
frameAncestors: ["'none'"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true },
referrerPolicy: { policy: 'no-referrer' },
}));
// 2) CORS 白名单(不是 *)
const ALLOWED_ORIGINS = ['https://app.example.com', 'https://admin.example.com'];
app.use(cors({
origin: (origin, callback) => {
if (!origin || ALLOWED_ORIGINS.includes(origin)) return callback(null, true);
return callback(new Error('CORS not allowed'));
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
// 3) 生产环境关闭详细错误
app.use((err, req, res, next) => {
if (app.get('env') === 'production') {
res.status(500).json({ error: 'Internal Server Error' });
} else {
res.status(err.status || 500).json({ error: err.message, stack: err.stack });
}
});
常见误配:
- Helmet 的
contentSecurityPolicy配了script-src: 'unsafe-inline',等于没配; - CORS 的
origin设为回显请求方(等价于*但带 credentials)。
快速自检清单
| 检查项 | 风险等级 | 条目编号 | 是否通过 | |--------|---------|---------|---------| | 所有资源 ID 端点已做所有权校验 | 高 | API1 | ☐ | | 登录接口有限流 + JWT 含 exp | 高 | API2 | ☐ | | JSON body 限制 ≤ 1MB | 中 | API4 | ☐ | | 分页接口 limit 强制 ≤ 100 | 中 | API4 | ☐ | | 含 URL 参数的端点已做域名白名单 | 高 | API7 | ☐ | | 禁止 302 跳转到内网地址 | 高 | API7 | ☐ | | Helmet 安全头已启用 | 中 | API8 | ☐ | | CORS 未全开(非 *) | 高 | API8 | ☐ | | 生产环境 404/500 不暴露堆栈 | 中 | API8 | ☐ | | JWT secret 从环境变量读取 | 高 | API2 | ☐ |
交付说明
本交付物覆盖 OWASP API Security Top 10 (2023) 中的 5 个高频条目(API1 BOLA、API2 Broken Authentication、API4 Unrestricted Resource Consumption、API7 SSRF、API8 Security Misconfiguration),每个条目均包含:漏洞简述(≤3 句)、可复现的检测命令(curl/node 脚本)、Express 4 风格修复代码、1-2 条常见误配。文末附 10 行快速自检表格(检查项/风险等级/条目编号/是否通过四列)。全文 Markdown 格式,≥1500 字,代码基于 Node.js 18+ / Express 4 / PostgreSQL 15 技术栈,可直接 copy 运行或审查。