首页教程JSON安全注意事项
安全实践

JSON 安全注意事项:防注入、数据验证与安全传输

📅 2024-01-20⏱ 阅读约 14 分钟👤 适合中级开发者

本文目录

  1. JSON注入攻击原理与防御
  2. 将JSON输出到HTML的XSS风险
  3. JSON Schema数据验证
  4. 敏感数据的安全处理
  5. 大体积JSON的DoS风险
  6. JSON与HTTPS安全传输
  7. 安全开发检查清单

JSON 格式本身是安全的,但在实际应用中,不正确地处理 JSON 数据会带来严重的安全漏洞。本文从攻击者视角分析常见的 JSON 安全问题,并提供具体的防御代码示例,帮助开发者构建更安全的应用。

1. JSON 注入攻击

JSON 注入发生在服务端将用户输入直接拼接到 JSON 字符串中,而没有进行正确的转义处理。这可能导致攻击者篡改 JSON 结构,绕过身份验证或修改数据。

攻击示例

假设服务端代码直接拼接用户名:

// ⚠️ 危险代码 - 直接拼接用户输入
const username = req.body.username;  // 攻击者输入: admin","role":"admin
const json = `{"username": "${username}", "role": "user"}`;
// 生成结果: {"username": "admin","role":"admin", "role": "user"}
// 某些解析器会取最后一个 role 值,但某些取第一个!

防御方法

永远不要手动拼接 JSON 字符串,始终使用语言内置的序列化方法:

// ✅ 安全做法 - 用对象构建再序列化
const data = {
  username: req.body.username,  // 自动转义特殊字符
  role: "user"
};
const json = JSON.stringify(data);

// Python 也一样
import json
data = {"username": username, "role": "user"}
json_str = json.dumps(data)  # 自动安全处理

2. 将 JSON 输出到 HTML 页面的 XSS 风险

当你把 JSON 数据内嵌到 HTML 页面中(比如服务端渲染场景),需要格外小心 XSS(跨站脚本)攻击。

危险场景

<!-- ⚠️ 危险:直接将 JSON 内嵌到 script 标签 -->
<script>
  var data = {name: "用户输入的内容"};
  // 如果 name 是 "</script><script>alert(1)</script>"
  // 浏览器会误将其解析为 HTML,执行恶意脚本
</script>

安全做法

// 方法1:在 JSON.stringify 后对特殊字符进行 HTML 转义
function safeJsonEmbed(data) {
  return JSON.stringify(data)
    .replace(/</g, '\\u003c')   // < → \u003c
    .replace(/>/g, '\\u003e')   // > → \u003e
    .replace(/&/g, '\\u0026')    // & → \u0026
    .replace(/'/g, '\\u0027');   // ' → \u0027
}

// HTML 中使用
<script>
  var data = <?= safeJsonEmbed($data) ?>;
</script>

// 方法2:使用 data 属性传递(更推荐)
<div id="app-data" data-config='<?= htmlspecialchars(json_encode($data)) ?>'></div>
<script>
  var data = JSON.parse(document.getElementById('app-data').dataset.config);
</script>

3. JSON Schema 数据验证

接收外部传入的 JSON 数据时,必须对其进行结构和类型验证,不能假设数据格式一定符合预期。JSON Schema 是验证 JSON 数据结构的标准方案。

JSON Schema 基本示例

// 定义 Schema:用户注册请求的格式要求
const userSchema = {
  "type": "object",
  "required": ["username", "email", "age"],
  "properties": {
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 20,
      "pattern": "^[a-zA-Z0-9_]+$"  // 只允许字母数字下划线
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150
    },
    "role": {
      "type": "string",
      "enum": ["user", "editor"]  // 只允许这两个值,防止权限提升
    }
  },
  "additionalProperties": false  // 禁止额外字段!
};

使用 ajv 库验证(Node.js)

const Ajv = require('ajv');
const ajv = new Ajv();

const validate = ajv.compile(userSchema);
const valid = validate(req.body);

if (!valid) {
  return res.status(400).json({
    error: "数据格式不合法",
    details: validate.errors
  });
}
// 验证通过,安全地使用数据
⚠️ 特别注意 additionalProperties: false——如果不设置这一项,攻击者可以传入任意额外字段,可能导致"属性污染"(Mass Assignment)漏洞,让攻击者意外修改不应该修改的字段(如 isAdmin、role 等)。

4. 敏感数据的安全处理

不应在 JSON 中明文传输的数据

API 响应中的数据脱敏示例

// ⚠️ 错误:返回了密码哈希
{
  "userId": 1001,
  "username": "zhangsan",
  "passwordHash": "$2b$10$abc123...",  // 不该返回!
  "email": "zhangsan@example.com",
  "phone": "13812345678"              // 完整手机号
}

// ✅ 正确:敏感字段脱敏或不返回
{
  "userId": 1001,
  "username": "zhangsan",
  "email": "z***n@example.com",
  "phone": "138****5678"
}

日志中的 JSON 数据脱敏

// 记录日志前,先清理敏感字段
function sanitizeForLog(data) {
  const sensitiveKeys = ['password', 'token', 'secret', 'creditCard', 'ssn'];
  const clone = JSON.parse(JSON.stringify(data));
  
  function redact(obj) {
    for (const key in obj) {
      if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
        obj[key] = '[REDACTED]';
      } else if (typeof obj[key] === 'object') {
        redact(obj[key]);
      }
    }
  }
  
  redact(clone);
  return clone;
}

// 安全地记录日志
logger.info('用户请求:', sanitizeForLog(req.body));

5. 大体积 JSON 的 DoS 风险

恶意用户可能发送巨型 JSON 数据(如嵌套 10000 层的对象),导致服务器内存耗尽或 CPU 占满(Billion Laughs 攻击的 JSON 变体)。

防御措施

// Express.js:限制请求体大小
const express = require('express');
app.use(express.json({
  limit: '1mb',      // 限制请求体最大 1MB
  strict: true,      // 只接受对象和数组(不接受裸字符串/数字)
}));

// 同时限制嵌套深度(自定义中间件)
function limitJsonDepth(maxDepth) {
  return (req, res, next) => {
    function depth(obj, d = 0) {
      if (d > maxDepth) throw new Error('JSON嵌套过深');
      if (obj && typeof obj === 'object') {
        for (const v of Object.values(obj)) depth(v, d + 1);
      }
    }
    try { depth(req.body); next(); }
    catch (e) { res.status(400).json({error: e.message}); }
  };
}
app.use(limitJsonDepth(20));

6. JSON 与 HTTPS 安全传输

无论 JSON 数据多么安全地构建,如果通过 HTTP(非 HTTPS)传输,中间人攻击者仍然可以读取和篡改内容。

安全开发检查清单

在处理 JSON 数据时,确保以下每一项都已执行:

💡 在开发过程中,使用我们的 JSON格式化工具 可以快速检查 JSON 数据结构,确保没有意外包含敏感字段。工具完全在本地运行,数据不会上传服务器。