ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JSON注入漏洞深度解析:原理、利用与安全防御实践

2026/8/13 7:44:23 拓冰建站 浏览量
JSON注入漏洞深度解析:原理、利用与安全防御实践 在实际 Web 开发和安全测试中JSON 作为一种轻量级的数据交换格式几乎无处不在。无论是前后端 API 通信、配置文件存储还是微服务间的数据传输JSON 都扮演着核心角色。然而当开发者或安全工程师将注意力集中在 SQL 注入、XSS 等传统漏洞时JSON 数据流中潜藏的注入风险往往被忽视。JSON 注入与 SQL 注入在原理上相似都是由于对用户输入未做充分验证和过滤导致攻击者能够篡改数据结构的语义从而引发数据泄露、权限绕过甚至远程代码执行等严重后果。理解 JSON 注入对于构建健壮的 Web 应用和进行有效的渗透测试都至关重要。本文将以开发者和安全测试人员的双重视角深入解析 JSON 注入的原理、场景、利用方式及防御策略。我们将从 JSON 的基本结构讲起逐步深入到注入点的发现、Payload 的构造最后给出在代码层面和架构层面的加固方案。无论你是正在学习 Kali Linux 渗透测试的安全爱好者还是负责后端 API 开发的工程师都能通过本文建立起对 JSON 注入的清晰认知和实战应对能力。1. 理解 JSON 注入它是什么以及为什么危险在深入技术细节之前我们首先要厘清 JSON 注入的核心概念。很多人会混淆 JSON 注入与涉及 JSON 的其他攻击如 JSON Hijacking、XXE via JSON因此明确其定义和边界是第一步。1.1 JSON 注入的本质JSON 注入是一种注入类漏洞。其根本原因是应用程序在动态构造 JSON 数据时未经妥善处理就直接拼接了用户可控的输入。攻击者通过向这些输入点注入特殊的 JSON 语法字符如引号、花括号{}、方括号[]、冒号:、反斜杠\等可以破坏原有 JSON 的结构注入新的键值对甚至完全篡改 JSON 数据的语义。它与 SQL 注入的逻辑如出一辙本应作为“数据”处理的内容因为包含了“代码”此处指 JSON 语法控制字符而被解析器当作了“指令”执行。一个简单的类比是你让用户填写“姓名”本意是得到一个字符串数据张三但用户输入了张三, isAdmin: true。如果后端直接拼接最终生成的 JSON 可能就从{name: 张三}变成了{name: 张三, isAdmin: true}从而赋予了用户非预期的管理员权限。1.2 与相似漏洞的区分为了避免概念混淆这里简要区分几种常见的与 JSON 相关的安全问题JSON 注入 (JSON Injection)本文核心。漏洞发生在服务器端生成或拼接 JSON 时。攻击者输入被直接拼接到 JSON 字符串中改变了 JSON 结构。JSON 劫持 (JSON Hijacking)一种历史悠久的攻击主要针对老式浏览器。通过script标签窃取返回的 JSON 数组。现代浏览器通过禁止跨域脚本加载和默认使用 JSONP 回调包装等方式已极大缓解此问题。通过 JSON 的 XXE (XML External Entity)如果后端使用某些不安全的 XML 解析器来处理 JSON例如将 JSON 转换为 XML 后再处理攻击者可能通过注入特定的 XML 结构来触发 XXE。这本质是 XML 解析器的问题而非 JSON 本身。不安全的反序列化 (Insecure Deserialization)这是另一个层面的严重漏洞。当服务器接收到一个 JSON 字符串并使用不安全的反序列化方法如 PHP 的unserialize()、Java 中ObjectInputStream配合某些库将其转换为对象时可能触发远程代码执行。这与“注入”改变结构不同是反序列化过程本身的风险。理解这些区别有助于我们在不同的环节数据生成、数据传输、数据解析采取正确的防护措施。1.3 常见的危险场景JSON 注入通常出现在以下场景配置信息动态生成应用根据用户输入如用户名、主题动态生成前端配置 JSON。API 响应拼接后端将数据库查询结果与用户提供的参数拼接形成 API 响应。模板渲染在服务器端渲染SSR时将用户数据直接嵌入到script标签内的 JSON 对象中。日志记录将用户输入直接记录为 JSON 格式的日志可能影响日志分析系统。NoSQL 数据库查询某些 NoSQL 数据库如 MongoDB接受 JSON 格式的查询语句。如果直接拼接用户输入构建查询则可能引发 NoSQL 注入其原理与 JSON 注入高度相似。2. 环境准备与概念验证要真正理解一个漏洞最好的方式是在可控的环境中进行复现。我们不会使用 Kali Linux 进行攻击演示以避免涉及攻击工具的具体使用而是搭建一个存在漏洞的简易 Web 应用从攻击者和防御者两个角度观察 JSON 注入的发生与防御。这比单纯的理论讲解更具象。2.1 搭建一个存在 JSON 注入漏洞的示例应用我们使用 Python 的 Flask 框架快速搭建一个后端服务。这个服务模拟一个“用户信息更新”的 API它接收用户输入并拼接成 JSON 返回给前端或者用于模拟后续处理。首先确保你的开发环境有 Python 3 和 pip。创建一个新的项目目录并安装 Flaskmkdir json-injection-demo cd json-injection-demo python3 -m venv venv # 创建虚拟环境可选但推荐 # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate pip install flask接下来创建app.py文件编写一个存在典型 JSON 注入漏洞的端点from flask import Flask, request, jsonify import json app Flask(__name__) # 存在漏洞的端点直接拼接用户输入构建JSON app.route(/update_profile_unsafe, methods[POST]) def update_profile_unsafe(): try: data request.get_json() if not data: return jsonify({error: No JSON data provided}), 400 username data.get(username, ) theme data.get(theme, light) # 用户可控制的主题参数 # 危险操作直接使用字符串格式化拼接JSON # 模拟生成一个用户配置对象 user_profile f{{username: {username}, theme: {theme}, role: user}} # 为了演示我们尝试解析它模拟后续处理 profile_dict json.loads(user_profile) # 在真实场景中这个 profile_dict 可能会被存入数据库或返回给前端 return jsonify({message: Profile updated (unsafe), profile: profile_dict}) except json.JSONDecodeError as e: return jsonify({error: Generated JSON is invalid, detail: str(e)}), 500 except Exception as e: return jsonify({error: Server error, detail: str(e)}), 500 if __name__ __main__: app.run(debugTrue, port5000)启动这个应用python app.py应用将在http://127.0.0.1:5000运行。2.2 发起注入攻击从简单到复杂现在我们使用curl或 Postman 等工具来模拟攻击者向这个漏洞端点发送恶意载荷。场景一破坏 JSON 结构导致服务错误这是最简单的攻击目的是使服务崩溃或返回错误。curl -X POST http://127.0.0.1:5000/update_profile_unsafe \ -H Content-Type: application/json \ -d {username: attacker, theme: dark\}} # 注入一个引号来破坏字符串发送这个请求后服务端拼接的字符串会是{username: attacker, theme: dark}, role: user}这显然是一个无效的 JSON在dark后多了一个}和json.loads()会抛出JSONDecodeError服务器返回 500 错误。这属于一种拒绝服务攻击。场景二注入新的键值对提升权限这是更危险的场景。假设应用后端会根据profile_dict中的role字段判断用户权限。curl -X POST http://127.0.0.1:5000/update_profile_unsafe \ -H Content-Type: application/json \ -d {username: attacker, theme: light\, \role\: \admin\, \ignored\: \}这个 Payload 需要仔细分析。用户输入的theme值为light\, \role\: \admin\, \ignored\: \后端拼接后生成的 JSON 字符串为{username: attacker, theme: light\, \role\: \admin\, \ignored\: \, role: user}当json.loads()解析这个字符串时它会看到username:attackertheme:light(注意后面的内容被转义了但解析后theme的值就是light,\, \role\: \admin\, \ignored\: \成为了theme字符串值的一部分这里需要修正) 实际上上面的 Payload 构造有误。正确的构造方式是利用闭合引号和逗号。让我们构造一个能正确解析的 Payloadcurl -X POST http://127.0.0.1:5000/update_profile_unsafe \ -H Content-Type: application/json \ -d {username: attacker, theme: light\, \role\: \admin\}}拼接后{username: attacker, theme: light\, \role\: \admin\}, role: user}这仍然是无效的 JSON。关键在于我们的漏洞代码是用f-string将theme变量的值包裹在双引号内的。所以要注入一个新的键值对我们需要先闭合theme值的双引号然后闭合整个theme键值对并添加一个逗号,注入我们的恶意键值对role: admin,为了保持 JSON 结构完整需要让剩下的代码变成注释或被忽略但这在标准 JSON 中很难。更直接的方法是让theme的值本身包含破坏性语法。实际上更典型的场景是后端没有用json.dumps()而是直接拼接字符串来构造一个将要被其他系统如数据库、前端JS解析的 JSON 字符串。我们修改一下漏洞代码来模拟这种场景# 另一个漏洞示例拼接JSON用于前端渲染 app.route(/get_config_unsafe) def get_config_unsafe(): user_id request.args.get(user_id, default) # 假设从数据库或其他地方获取了一些基础配置 base_config {appName: MyApp, version: 1.0, userId: # 危险直接将用户输入拼接到JSON数字位置 config_json base_config user_id } # 这个 config_json 可能会被内嵌到 HTML 的 script 标签中 return fscriptvar config {config_json}; console.log(config);/script攻击者可以访问http://127.0.0.1:5000/get_config_unsafe?user_id100, \isAdmin\: true生成的config_json为{appName: MyApp, version: 1.0, userId: 100, isAdmin: true }这样前端 JavaScript 得到的config对象就多了一个isAdmin: true的属性。通过以上两个例子你应该能直观感受到当字符串拼接遇到 JSON 语法时用户输入中的引号、花括号、逗号都变得极其危险。3. 挖掘与识别 JSON 注入点在安全测试中发现漏洞是第一步。对于 JSON 注入黑盒测试和白盒代码审计有不同的思路。3.1 黑盒测试渗透测试视角在不了解内部代码的情况下测试者需要通过输入和输出来推断是否存在漏洞。寻找 JSON 输入/输出点API 接口使用 Burp Suite、Postman 等工具拦截所有请求重点关注Content-Type: application/json的请求和响应。前端 JavaScript查看网页源码寻找script标签内由服务器动态生成的var config {...}或JSON.parse(...)的调用。数据导出/下载某些功能会导出 JSON 格式的数据文件。测试输入特殊字符探测在 JSON 字符串值的位置尝试输入、、\、{}、[]、:、,。观察响应500 内部服务器错误可能意味着注入的字符破坏了 JSON 结构导致解析失败。响应内容变化对比注入前后 API 返回的 JSON 结构看是否多出了键值对或者值被意外修改。前端 JavaScript 错误如果 JSON 被内嵌到script中无效的 JSON 会导致 JS 语法错误在浏览器控制台可以看到。尝试闭合与注入假设你修改的参数对应 JSON 中的key: YOUR_INPUT。尝试输入test观察是否出错。如果出错说明引号被解析。尝试输入test\, \hack\: \injected。如果后端是拼接且未过滤你可能会在响应中看到hack: injected这个新字段。自动化工具辅助一些 DAST动态应用安全测试工具可以检测 JSON 注入但其效果严重依赖于测试用例的完备性。手动测试和理解业务逻辑仍然不可替代。3.2 白盒审计开发与代码审计视角审查代码是发现漏洞最直接有效的方式。重点关注以下模式字符串拼接/格式化构造 JSON搜索代码中的、f...、format()、StringBuilder、concat等看它们是否用于构建 JSON 字符串。# Python 危险模式 json_str {user: username , status: active} json_str f{{user: {username}, status: active}}// Java 危险模式 String jsonStr {\user\: \ username \, \status\: \active\};// Node.js 危险模式 let jsonStr {user: ${username}, status: active};不安全的模板渲染在 Flask Jinja2、Django Templates、Spring Thymeleaf 等模板中如果直接将用户变量放入script标签且未正确转义就会产生漏洞。!-- 危险Jinja2 模板中未转义 -- script var userData {{ user_provided_json|safe }}; !-- 使用了 |safe 过滤器 -- /script查找序列化/反序列化函数虽然主要讲注入但也要留意不安全的反序列化。搜索json.loads()/json.dumps()(Python),JSON.parse()/JSON.stringify()(JavaScript),ObjectMapper.readValue()(Java) 等函数的调用检查其输入是否完全可信。4. 从根源防御安全的 JSON 处理实践防御 JSON 注入的核心原则非常简单永远不要使用字符串拼接或格式化来创建 JSON。始终使用标准库或成熟框架提供的序列化/反序列化方法。4.1 正确使用序列化库修复漏洞让我们修复第 2 节中的漏洞示例。正确的方法是使用json.dumps()将字典/对象转换为 JSON 字符串。修改app.py增加一个安全的端点import json from flask import Flask, request, jsonify app Flask(__name__) # 安全的端点使用 json.dumps 构建 JSON app.route(/update_profile_safe, methods[POST]) def update_profile_safe(): try: data request.get_json() if not data: return jsonify({error: No JSON data provided}), 400 username data.get(username, ) theme data.get(theme, light) # 安全操作先构建字典再用 json.dumps 转换为 JSON 字符串 profile_dict { username: username, # 库会自动处理 username 中的特殊字符 theme: theme, # 库会自动处理 theme 中的特殊字符 role: user } # json.dumps 会确保生成的字符串是语法正确的 JSON # user_profile_json json.dumps(profile_dict) # 如果需要字符串形式 # 但我们通常直接返回字典Flask 的 jsonify 会帮我们处理 return jsonify({message: Profile updated (safe), profile: profile_dict}) except Exception as e: return jsonify({error: Server error, detail: str(e)}), 500 # 安全的配置生成端点 app.route(/get_config_safe) def get_config_safe(): user_id request.args.get(user_id, default) try: # 确保 user_id 是预期的类型例如数字 user_id_int int(user_id) except ValueError: user_id_int 0 config_dict { appName: MyApp, version: 1.0, userId: user_id_int # 直接使用数字而不是拼接字符串 } # 使用 json.dumps 确保安全然后嵌入HTML config_json json.dumps(config_dict) return fscriptvar config {config_json}; console.log(config);/script关键区别在安全版本中我们首先构建一个 Python 字典profile_dict。当我们将值如username,theme放入字典时它们只是 Python 字符串对象。json.dumps(profile_dict)或jsonify(profile_dict)会负责将整个字典安全地转换为符合 JSON 语法的字符串。这个过程会自动处理在字符串值外添加双引号。对字符串内部的特殊字符进行转义例如变成\\变成\\换行符变成\n。正确生成逗号、冒号来分隔键值对和元素。因此即使用户输入包含、\或}它们也会被转义为字符串内容的一部分而不会破坏 JSON 结构。例如用户输入theme为dark\}在安全的 JSON 中会被表示为dark\\\}。4.2 输入验证与净化尽管使用安全的序列化库是治本之策但输入验证作为一道前置防线同样重要。它不能替代安全序列化但可以增加一层保障。类型检查确保输入符合预期类型字符串、数字、布尔值。例如user_id应该是数字。长度限制防止过长的输入导致资源消耗。白名单验证对于有固定选项的字段如theme只能是light或dark使用白名单。业务逻辑验证检查输入是否符合业务规则。def validate_user_input(data): errors [] username data.get(username, ).strip() theme data.get(theme, light).strip() # 长度限制 if len(username) 50: errors.append(Username too long) if len(theme) 20: errors.append(Theme name too long) # 白名单验证 allowed_themes [light, dark, blue] if theme not in allowed_themes: errors.append(fTheme must be one of {allowed_themes}) # 简单的字符集白名单示例根据实际情况调整 # 允许字母、数字、部分符号 import re if not re.match(r^[a-zA-Z0-9_\-\.]$, username): errors.append(Username contains invalid characters) return errors, username, theme4.3 输出编码与上下文安全当 JSON 需要嵌入到其他上下文如 HTML、JavaScript时需要额外的编码。嵌入 HTML如果 JSON 字符串要作为 HTML 标签的属性值需要使用 HTML 实体编码。嵌入 JavaScript虽然使用json.dumps()生成的 JSON 字符串对于 JavaScript 的JSON.parse()是安全的但如果要直接内嵌到script标签中作为 JavaScript 对象字面量如var config {{json_str}};则需要确保整个字符串被正确包裹。最佳实践是使用json.dumps()生成 JSON 字符串。将这个字符串赋值给一个 JS 变量或者通过JSON.parse()来解析。!-- 安全做法 -- script var configJson {{ config_json|tojson|safe }}; // Flask 的 tojson 过滤器会处理 var config JSON.parse(configJson); // 或者更简单如果后端框架支持 var config {{ config_dict|tojson }}; /script在 Flask 中|tojson过滤器会自动调用json.dumps并标记为安全非常适合此场景。4.4 安全开发清单将以下要点作为代码审查和开发时的检查清单检查项不安全做法安全做法构建 JSON字符串拼接 (,f-string,format)使用标准库序列化 (json.dumps(),JSON.stringify(),ObjectMapper.writeValueAsString())处理用户输入直接放入数据结构先进行验证类型、长度、白名单和净化嵌入到 HTML/JS直接拼接进script标签使用框架提供的安全输出函数如 Flask 的 内容类型响应头Content-Type不正确或缺失确保 API 响应头包含Content-Type: application/jsonNoSQL 查询拼接字符串构建查询使用驱动提供的参数化查询或构建器 API如 Mongoose 的find({field: value})5. 进阶JSON 注入与 NoSQL 注入的交集在现代 Web 架构中MongoDB 等 NoSQL 数据库的使用非常普遍。这些数据库通常使用类 JSON 的查询语法如 BSON。如果应用程序通过拼接字符串来构建查询就会产生NoSQL 注入漏洞其原理和 JSON 注入高度同源。一个危险的 MongoDB 查询示例伪代码// 假设从请求中获取用户名和密码 let username req.body.username; let password req.body.password; // 危险直接拼接查询对象 let query {username: ${username}, password: ${password}}; // 或者在某些动态语言中更常见的是构建一个对象但键名或值来自用户输入 let query {}; query[userProvidedKey] userProvidedValue; // 如果用户控制键名也可能有问题 db.users.find(JSON.parse(query)); // 将字符串解析为对象后查询攻击者可以输入username:adminpassword:{$ne: null}(MongoDB 的$ne表示不等于) 最终查询条件变为{username: admin, password: {$ne: null}}这意味着“查找用户名为 admin 且密码不为空的文档”很可能绕过了密码验证。防御措施使用官方驱动提供的参数化查询或查询构建器。严格类型转换将用户输入转换为期望的类型如字符串、数字。白名单验证查询运算符如果允许用户指定一些查询选项必须严格限制可用的运算符如$eq,$gt并禁止$where,$expr等可能执行代码的运算符。实施最小权限原则数据库连接使用具有最小必要权限的账户。6. 总结与最佳实践JSON 注入是一个因不良编程习惯而导致的中高风险漏洞。其原理简单但危害不容小觑。回顾全文我们可以总结出以下核心实践要点绝对禁止拼接这是铁律。在任何语言、任何框架中都不要使用字符串拼接、格式化来生成 JSON 或类 JSON如 NoSQL 查询的结构。信任你的序列化库Python 的json模块、JavaScript 的JSON对象、Java 的 Jackson/Gson、C# 的JsonSerializer等都是经过千锤百炼的工具。它们能正确处理特殊字符的转义确保输出语法正确。让你的数据以原生对象/字典的形式存在只在最后一步交给这些库去序列化。明确上下文正确编码区分“数据”和“代码”。JSON 是数据格式。当它需要被放入 HTML 或 JavaScript 代码上下文时要使用对应的编码方式如 HTML 实体编码、JS 字符串转义。现代 Web 框架通常提供了辅助函数如|tojson、json_script优先使用它们。进行深度防御安全序列化是核心但输入验证、输出编码、使用安全的 HTTP 头如Content-Type: application/json共同构成了深度防御体系。在安全测试中纳入此项在进行代码审计或渗透测试时将 JSON 构造点作为重点检查对象。使用特殊字符进行模糊测试并仔细比对请求与响应。对于正在学习 Kali Linux 和安全测试的读者理解 JSON 注入能帮助你更精准地发现和报告此类漏洞。而对于开发者而言遵循上述实践能从根本上杜绝此类问题写出更安全、更健壮的代码。安全不是事后补救的特性而是从一开始就应融入开发流程的基本要求。