JSON 安全最佳实践

通用 阅读约 17 分钟
JSON安全OWASP验证最佳实践

保护你的应用免受 JSON 相关漏洞的攻击。学习注入攻击、原型污染、DoS 防护、输入验证以及安全反序列化模式。

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 API 对比

语言 序列化 反序列化 美化输出 配置方式
Python json(内置) json.dumps(obj) json.loads(str) json.dumps(obj, indent=2) ensure_ascii, default, cls
JavaScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Java Jackson / Gson mapper.writeValueAsString(obj) mapper.readValue(str, Class.class) mapper.writerWithDefaultPrettyPrinter() 注解、Module、Feature
Go encoding/json(内置) json.Marshal(obj) json.Unmarshal(data, &obj) json.MarshalIndent(obj, "", " ") struct tag
C# System.Text.Json(内置) JsonSerializer.Serialize(obj) JsonSerializer.Deserialize<T>(str) WriteIndented = true JsonSerializerOptions
TypeScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Rust serde_json serde_json::to_string(&obj) serde_json::from_str::<T>(str) serde_json::to_string_pretty(&obj) serde attributes, custom (de)serializers

常见问题 (FAQ)

JSON 序列化时如何处理日期时间类型?

大多数语言的 JSON 库默认不支持日期时间类型。通常做法是序列化为 ISO 8601 格式字符串(如 "2026-07-15T10:30:00Z")或 Unix 时间戳,反序列化时再转换回日期时间对象。

如何忽略 JSON 序列化中的 null 值或空字段?

不同语言的实现方式不同:Python 可在自定义编码器中过滤 None 值;JavaScript 可使用 replacer 函数;Java Jackson 用 @JsonInclude 注解;Go 使用 omitempty struct tag;C# 设置 DefaultIgnoreCondition。

JSON 序列化时如何处理循环引用?

循环引用是 JSON 序列化的常见陷阱。标准 JSON 库通常会抛出异常或栈溢出。解决方案包括:使用 @JsonIdentityInfo 注解(Java Jackson)、实现自定义序列化器、将对象图转换为 DTO 后再序列化。

为什么我的私有字段没有被序列化?

JSON 序列化库通常只序列化公开(public)字段或带有 getter 的属性。Python 的 json 模块默认只序列化 dict 的公共键;Java Jackson 可通过 @JsonInclude 注解包含非公共成员。

JSON 序列化性能有哪些优化技巧?

1) 复用序列化器实例;2) 对于大型数据,使用流式 API;3) 使用编译时生成(C# Source Generator、Go easyjson);4) 避免过多嵌套层次;5) 使用数据库分页或分块传输大 JSON。

推荐工具

以下工具可以帮助你在 通用 开发中更高效地处理 JSON 数据:

相关文章