7 大 JSON 安全风险
JSON 是现代 API 中最常见的数据格式。理解其安全影响至关重要。
1. 注入攻击
SQL 注入
// ❌ 危险 - 字符串拼接
const query = `SELECT * FROM users WHERE data->>'name' = '${json.name}'`;
// ✅ 安全 - 参数化查询
const query = 'SELECT * FROM users WHERE data->>\'name\' = $1';
const result = await db.query(query, [json.name]);
NoSQL 注入
// ❌ 危险 - 接受 MongoDB 操作符
const filter = JSON.parse(userInput);
// 如果 userInput 是 {"$gt": ""},会匹配所有记录!
// ✅ 安全 - 验证结构
const filter = { name: userInput.name };
const result = await db.collection('users').find(filter);
命令注入
// ❌ 永远不要这样做
eval('var data = ' + jsonString);
// ❌ 或这样
new Function('return ' + jsonString)();
// ✅ 始终使用 JSON.parse
const data = JSON.parse(jsonString);
2. 原型污染
理解攻击原理
// 攻击者发送:
{
"__proto__": {
"isAdmin": true
}
}
// 合并后:
const user = {};
Object.assign(user, maliciousData);
console.log(user.isAdmin); // true - 原型被污染!
防护措施
// ✅ 使用递归 reviver 清理
function safeParse(json) {
const data = JSON.parse(json, (key, value) => {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
return undefined;
}
return value;
});
return data;
}
// ✅ 使用 Map 代替普通对象
const data = new Map();
data.set('name', '张三');
3. 拒绝服务
大载荷
const MAX_SIZE = 1024 * 1024; // 1MB
function parseWithLimit(json) {
if (Buffer.byteLength(json, 'utf8') > MAX_SIZE) {
throw new Error('载荷过大');
}
return JSON.parse(json);
}
深层嵌套
const MAX_DEPTH = 10;
function parseWithDepthLimit(json, maxDepth = MAX_DEPTH) {
function checkDepth(value, currentDepth) {
if (currentDepth > maxDepth) {
throw new Error('超过最大嵌套深度');
}
if (Array.isArray(value)) {
value.forEach(item => checkDepth(item, currentDepth + 1));
} else if (value && typeof value === 'object') {
Object.values(value).forEach(v => checkDepth(v, currentDepth + 1));
}
}
const data = JSON.parse(json);
checkDepth(data, 0);
return data;
}
循环引用
// ❌ 这会抛出异常
const obj = {};
obj.self = obj;
JSON.stringify(obj); // TypeError: 循环结构
// ✅ 检测并处理
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[循环引用]';
}
seen.add(value);
}
return value;
});
}
4. 数据泄露
敏感字段
// ❌ 暴露密码哈希
res.json(user);
// ✅ 移除敏感字段
function sanitizeUser(user) {
const { password, salt, ...safeUser } = user;
return safeUser;
}
// ✅ 或使用序列化库
class User {
toJSON() {
const { password, salt, ...safe } = this;
return safe;
}
}
错误信息
// ❌ 暴露内部细节
catch (err) {
res.status(500).json({ error: err.message, stack: err.stack });
}
// ✅ 通用错误信息
catch (err) {
console.error(err);
res.status(500).json({ error: '内部服务器错误' });
}
5. 输入验证
Schema 验证
const Ajv = require('ajv');
const ajv = new Ajv({ allErrors: true });
const schema = {
type: 'object',
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0, maximum: 150 }
},
required: ['name', 'email'],
additionalProperties: false
};
const validate = ajv.compile(schema);
function validateInput(data) {
if (!validate(data)) {
throw new Error(validate.errors.map(e => e.message).join(', '));
}
return data;
}
6. HTTPS 和传输安全
// ✅ 生产环境始终使用 HTTPS
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
next();
});
// ✅ 设置安全头
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Content-Security-Policy', "default-src 'self'");
next();
});
7. CORS 配置
// ✅ 限制来源
const cors = require('cors');
app.use(cors({
origin: ['https://yourdomain.com'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
安全检查清单
- 使用 schema 验证所有 JSON 输入
- 限制载荷大小(建议 1MB)
- 限制嵌套深度(建议 10 层)
- 数据库操作使用参数化查询
- 合并数据前先清理
- 序列化前移除敏感字段
- 生产环境使用 HTTPS
- 实施正确的 CORS 策略
- 记录安全事件用于监控
- 保持依赖更新
相关资源
- JSON Schema 验证器 — 验证你的 JSON
- JSON 格式化工具 — 格式化 JSON
- JSON 转义工具 — 安全转义 JSON 字符串
- JSON 最佳实践 — 通用最佳实践
- JSON Schema 指南 — Schema 验证