JSON注入漏洞攻防实战:从Kali Linux渗透测试到代码级防御
2026/8/13 14:10:57 网站建设 项目流程

如果你是一名开发者,最近在调试API接口时,是不是经常遇到这样的场景:前端传过来的JSON数据,后端解析后直接拼接到了SQL语句里,或者直接eval执行了?你心里可能隐隐觉得这不太安全,但又说不清具体风险在哪,更不知道攻击者会怎么利用。

这就是JSON注入。它不像SQL注入那样广为人知,但危害同样巨大,而且由于现代Web应用大量使用JSON进行数据交换,它正变得越来越普遍。很多人以为用了JSON.parse就万事大吉,实际上,如果解析后的数据被“信任”并用于不安全的上文(如数据库查询、系统命令、代码执行),漏洞就产生了。

本文不会堆砌晦涩的理论。我们将从一个渗透测试者(使用Kali Linux)和一名防御者(后端开发者)的双重视角,用最直白的语言和可操作的案例,带你彻底搞懂JSON注入。你会明白:

  1. 它到底是什么:不是解析JSON出错,而是数据被“误用”。
  2. 攻击者怎么利用它:我们将用Kali Linux下的工具模拟一次完整的攻击链。
  3. 你该如何防御:从代码层面给出具体、可落地的解决方案。

无论你是想提升系统安全性的开发者,还是对安全测试感兴趣的学习者,这篇文章都能让你获得即学即用的知识。我们现在开始。

1. 这篇文章真正要解决的问题:为什么你的“安全”JSON接口依然危险?

很多开发者对输入安全的认知还停留在“防SQL注入”和“防XSS”上。当接口采用JSON格式通信时,大家容易产生一种错觉:JSON是结构化数据,比原始的POST表单更安全,更何况我们用了框架自带的@RequestBodyjson_decode

危险恰恰隐藏在这种“安全感”背后。JSON注入的核心问题,不在于JSON格式本身,而在于应用程序如何处理解析后的JSON数据

想象这样一个流程:

  1. 前端发送:{"username": "admin", "action": "getProfile"}
  2. 后端用JSON.parse@RequestBody成功解析为对象。
  3. 后端代码直接将username的值,未经充分校验,拼接到了数据库查询语句中String sql = "SELECT * FROM users WHERE username = '" + parsedJson.username + "'";

如果攻击者发送的是{"username": "admin' OR '1'='1", "action": "getProfile"}呢?解析后,username的值是admin' OR '1'='1,拼接进SQL就会造成经典的SQL注入。攻击载荷是藏在合法的JSON结构里的

所以,本文要解决的第一个关键认知是:JSON注入是一种“上下文注入”漏洞。漏洞发生的上下文可能是:

  • SQL上下文:JSON数据值被拼接到SQL查询中。
  • OS命令上下文:JSON数据值被传递给Runtime.exec()system()等函数。
  • 代码执行上下文:JSON数据值被用于动态脚本执行(如JavaScript的eval、PHP的assert)。
  • NoSQL查询上下文:JSON数据值被直接用于构建MongoDB、Elasticsearch的查询对象。
  • 路径遍历上下文:JSON数据值被用于文件路径拼接。

作为开发者,你需要识别出代码中所有“解析JSON -> 使用数据”的链条,并判断使用环境是否安全。作为安全测试者,你需要知道如何构造隐藏在JSON中的攻击载荷,并利用工具高效测试。

接下来,我们将从攻击和防御两个角度,彻底拆解这个问题。

2. 基础概念与核心原理:什么是JSON注入?

让我们抛开教科书定义,用两句话讲清楚:

JSON注入(JSON Injection)是指攻击者通过向应用程序提交恶意构造的JSON数据,利用应用程序对JSON数据的错误处理方式,在目标系统上执行非预期操作的一种攻击手段。

它的原理基于一个简单的逻辑链条:

  1. 信任边界模糊:应用程序认为“既然数据是合法的JSON格式,且通过了框架解析,那么其内容就是可信的”。这是一个致命的错误假设。
  2. 上下文混淆:开发者没有区分“数据格式正确”和“数据内容安全”。JSON解析器只检查语法,不检查语义。
  3. 缺乏输出编码/校验:解析后的数据被直接用于拼接字符串(构造SQL、命令、代码等),而没有根据目标上下文进行转义或严格校验。

为了更清晰地理解它与其他注入漏洞的关系,请看下表:

漏洞类型攻击载体注入点上下文核心问题
SQL注入HTTP参数(如?id=1)、表单字段SQL查询语句用户输入被直接拼接到SQL命令中
命令注入HTTP参数、表单字段、系统变量操作系统命令(如bashcmd用户输入被直接拼接到系统命令中
JSON注入HTTP请求体(JSON格式)SQL、命令、代码、NoSQL查询等JSON解析后的数据值被直接拼接到危险上下文中
XSSHTTP参数、表单字段、数据库存储HTML、JavaScript上下文用户输入被直接输出到浏览器页面且未转义

关键区别:JSON注入的“包装”是JSON格式。攻击者需要将传统的攻击载荷(如' OR '1'='1)嵌入到JSON字符串的值中。对于防御者来说,传统的WAF或输入过滤器如果只检查原始字符串,可能因为JSON的引号、转义符而绕过检测。

3. 环境准备:搭建一个存在JSON注入漏洞的靶场

要理解攻击,最好的方式是亲手实践。我们将使用Kali Linux和一款经典的漏洞练习平台DVWA(Damn Vulnerable Web Application)来搭建测试环境。

3.1 Kali Linux 基础准备

Kali Linux是渗透测试和安全研究的首选系统,预装了海量工具。如果你还没有Kali,可以通过以下方式获取:

  • 虚拟机安装:从 Kali官网 下载ISO镜像,在VMware或VirtualBox中安装。这是最推荐的方式,隔离性好。
  • Docker容器docker pull kalilinux/kali-rolling,快速获取一个Kali环境。
  • 云实例:在AWS、Azure或GCP上创建实例并安装Kali。

确保你的Kali系统能正常联网,并更新软件源:

sudo apt update && sudo apt upgrade -y

3.2 安装并配置DVWA

DVWA是一个故意设计存在漏洞的PHP/MySQL应用,非常适合学习。

  1. 安装LAMP栈:DVWA需要Web服务器和数据库。
    sudo apt install -y apache2 mariadb-server php php-mysql libapache2-mod-php php-gd
  2. 下载DVWA
    cd /var/www/html sudo git clone https://github.com/digininja/DVWA.git sudo chown -R www-data:www-data DVWA/
  3. 配置数据库
    sudo mysql -u root # 在MySQL提示符下执行 CREATE DATABASE dvwa; CREATE USER 'dvwa'@'localhost' IDENTIFIED BY 'p@ssw0rd'; GRANT ALL PRIVILEGES ON dvwa.* TO 'dvwa'@'localhost'; FLUSH PRIVILEGES; EXIT;
  4. 配置DVWA
    cd /var/www/html/DVWA/config sudo cp config.inc.php.dist config.inc.php sudo nano config.inc.php
    修改以下关键配置:
    $_DVWA[ 'db_server' ] = '127.0.0.1'; $_DVWA[ 'db_database' ] = 'dvwa'; $_DVWA[ 'db_user' ] = 'dvwa'; $_DVWA[ 'db_password' ] = 'p@ssw0rd'; $_DVWA[ 'default_security_level' ] = 'low'; // 将安全级别设为“低”,方便演示
  5. 重启服务并访问
    sudo systemctl restart apache2
    在Kali的浏览器中访问:http://localhost/DVWA/setup.php。点击页面底部的“Create / Reset Database”按钮初始化数据库。完成后,使用默认账号admin/password登录。

至此,一个包含多种漏洞(包括我们等下要利用的)的测试环境就准备好了。

4. 核心攻击流程拆解:以JSON到SQL注入为例

DVWA的“SQL Injection”关卡通常接收GET参数?id=1。为了演示JSON注入,我们需要稍微修改一下它的代码,模拟一个接收JSON参数的API接口。这能让我们更贴近真实场景。

4.1 构造一个易受攻击的JSON API端点

  1. 在DVWA目录下创建一个新文件:
    sudo nano /var/www/html/DVWA/vulnerabilities/json_sqli/
    注意:实际路径可能需要你手动创建json_sqli目录。更简单的方法是,我们直接修改一个现有文件。这里为了概念清晰,我们描述逻辑,你可以用以下PHP代码创建一个测试文件test_json.php放在DVWA根目录下。
    <?php // test_json.php - 一个存在JSON注入漏洞的示例端点 header('Content-Type: application/json'); // 模拟从请求体获取JSON数据 $json_input = file_get_contents('php://input'); $data = json_decode($json_input, true); if (!$data) { echo json_encode(['error' => 'Invalid JSON']); exit; } $user_id = $data['id'] ?? '1'; // 危险:直接从JSON中取id,未做任何过滤 // 危险:直接拼接SQL查询 $sql = "SELECT first_name, last_name FROM users WHERE user_id = '$user_id'"; // 连接数据库并执行查询(此处省略具体连接代码,假设已连接) // $result = mysqli_query($link, $sql); // ... 处理结果并输出JSON ... // 为了演示,我们只回显构造的SQL语句 echo json_encode([ 'received_id' => $user_id, 'generated_sql' => $sql, 'warning' => 'This endpoint is vulnerable!' ]); ?>
    这段代码的致命问题json_decode能成功解析{"id": "1"},也能解析{"id": "1' OR '1'='1"}。后者会导致SQL语句被篡改。

4.2 使用Kali工具进行渗透测试

在真实测试中,我们不会手动修改源码,而是使用工具扫描和探测。这里我们使用Kali自带的Burp Suitesqlmap来演示。

场景:假设我们发现一个API端点http://target.com/api/user/profile接受JSON格式的POST请求,参数是{"userId": "12345"}

步骤一:使用Burp Suite拦截与重放
  1. 在Kali中启动Burp Suite(Community版即可)。
  2. 配置浏览器代理(如Firefox)为127.0.0.1:8080,并安装Burp的CA证书。
  3. 在浏览器中访问目标应用,并提交一个正常的JSON请求。Burp会拦截到这个请求。
  4. 将请求发送到Burp的Repeater模块,方便我们修改和重放。
  5. 在Repeater中,将请求体修改为潜在的恶意载荷:
    {"userId": "12345' AND '1'='2"}
    或者尝试闭合JSON字符串并注释掉后续内容:
    {"userId": "\" OR 1=1;-- "}
    观察响应。如果响应与正常请求不同(如返回了所有用户数据或报错),说明可能存在注入点。
步骤二:使用sqlmap进行自动化注入测试

sqlmap是自动化SQL注入检测和利用的神器。它可以处理JSON格式的注入点。

  1. 将Burp拦截到的请求保存到一个文件(如request.txt)。请求应包含完整的HTTP头。

  2. 在终端中使用sqlmap:

    sqlmap -r request.txt --level=3 --risk=2 --batch
    • -r request.txt:从文件加载HTTP请求。
    • --level=3:提高测试等级,增加对User-AgentReferer等头的测试。
    • --risk=2:提高风险等级,尝试更危险的负载。
    • --batch:以非交互模式运行,自动选择默认选项。
  3. sqlmap会自动识别JSON参数(如JSON id),并对其进行一系列注入测试。如果存在漏洞,它会给出数据库类型、版本、甚至直接dump数据。

关键技巧:如果参数在JSON中,sqlmap可能需要指定参数前缀。可以使用--data--param-del

sqlmap -u "http://target.com/api/user/profile" --data='{"userId":"*"}' --param-del="*" --batch

这里*标记了注入点。

通过这个流程,你就能看到,一个看似“安全”的JSON API,是如何被传统注入工具轻易攻破的。攻击者不需要理解你的业务逻辑,工具会自动完成探测和利用。

5. 不仅仅是SQL:其他类型的JSON注入示例

JSON数据可以被误用到各种危险上下文中。以下是几个常见场景的代码示例:

5.1 命令注入(Node.js示例)

// 危险代码 const express = require('express'); const { exec } = require('child_process'); const app = express(); app.use(express.json()); app.post('/api/ping', (req, res) => { const host = req.body.host; // 直接从JSON中获取 // 致命漏洞:用户输入直接进入命令 exec(`ping -c 4 ${host}`, (error, stdout, stderr) => { res.send({ output: stdout }); }); });

攻击载荷{"host": "8.8.8.8; cat /etc/passwd"}。这会执行ping命令后,继续执行cat /etc/passwd

5.2 NoSQL注入(MongoDB示例)

// 危险代码(使用Mongoose) app.post('/api/login', async (req, res) => { const { username, password } = req.body; // 直接将用户输入传入查询对象 const user = await User.findOne({ username: username, password: password // 假设密码是明文存储(这本身也是问题) }); if (user) { res.json({ success: true }); } else { res.json({ success: false }); } });

攻击载荷{"username": "admin", "password": {"$ne": null}}。MongoDB会将其解析为查询条件password != null,这很可能为真,从而绕过密码验证。

5.3 代码注入(PHP示例)

// 危险代码 $data = json_decode(file_get_contents('php://input'), true); $functionName = $data['callback']; // 致命漏洞:用户控制的字符串被直接用于函数调用 if (function_exists($functionName)) { $result = $functionName(); echo json_encode($result); }

攻击载荷{"callback": "system", "args": "rm -rf /"}。虽然这个例子需要args也被传递,但它说明了直接使用未经验证的函数名是极度危险的。

6. 防御策略:从开发源头堵住漏洞

理解了攻击原理,防御就有了清晰的方向。核心原则是:永远不要信任用户输入,无论它是什么格式。

6.1 输入验证与白名单

这是第一道,也是最有效的防线。验证数据内容,而不仅仅是格式。

  • 类型检查:确保id是整数,username是特定格式的字符串。
  • 范围/长度检查:数字是否在合理范围内?字符串长度是否有限制?
  • 白名单验证:对于枚举值(如状态、类型),只接受预定义的几个值。
// Java Spring Boot 示例 - 使用注解进行验证 public class UserRequest { @Min(1) // id必须大于0 private Integer id; @Pattern(regexp = "^[a-zA-Z0-9_]{3,20}$") // 用户名格式白名单 private String username; // getters and setters } @PostMapping("/api/user") public ResponseEntity<?> updateUser(@Valid @RequestBody UserRequest request) { // 如果验证失败,会抛出MethodArgumentNotValidException // 业务逻辑... }

6.2 参数化查询(应对SQL注入)

绝对不要拼接SQL字符串。使用预编译语句(Prepared Statements)或ORM框架的参数化查询。

# Python Flask + SQLAlchemy 示例 - 安全做法 from flask import request, jsonify from models import User from database import db_session @app.route('/api/user/<int:user_id>', methods=['GET']) def get_user(user_id): # 从路径获取,已确保是int # 使用ORM,自动参数化 user = User.query.filter_by(id=user_id).first() # 或者使用原始SQL,但也要参数化 # from sqlalchemy import text # stmt = text("SELECT * FROM users WHERE id = :id") # result = db_session.execute(stmt, {'id': user_id}) return jsonify(user.to_dict())

6.3 安全的NoSQL查询

避免直接将用户输入传递给查询构造器。使用框架提供的安全方法。

// Node.js + Mongoose 安全示例 app.post('/api/login', async (req, res) => { const { username, password } = req.body; // 先查找用户 const user = await User.findOne({ username: username }); // 然后使用安全的比较函数(如bcrypt)验证密码 if (user && await bcrypt.compare(password, user.passwordHash)) { res.json({ success: true }); } else { res.json({ success: false }); } });

6.4 避免动态代码执行

永远不要用eval()setTimeout(userInput)Function(userInput)或反序列化未经验证的数据(如PHP的unserialize())。 如果需要动态调用,请使用映射到白名单的方式:

const ALLOWED_CALLBACKS = { 'getData': getDataFunction, 'calculate': calculateFunction }; const funcName = req.body.callback; const func = ALLOWED_CALLBACKS[funcName]; if (func) { func(); } else { throw new Error('Invalid callback'); }

6.5 输出编码与上下文感知

如果必须将用户输入输出到其他上下文(如生成动态HTML、JavaScript),必须进行编码。

  • HTML上下文:使用HTML实体编码(如<变成&lt;)。
  • JavaScript上下文:使用JavaScript字符串编码。
  • 系统命令上下文:避免直接拼接。如果必须,使用严格的参数化方式(如将参数作为数组传递给execFile)。
# Python subprocess 安全示例 import subprocess import shlex host = request.json['host'] # 假设已经过白名单验证,如只允许IP地址 # 错误:subprocess.run(f'ping -c 4 {host}', shell=True) # 正确:使用参数列表,避免shell subprocess.run(['ping', '-c', '4', host], check=True)

6.6 使用安全的JSON解析配置

有些JSON解析库有“特性”可能导致问题。例如,在PHP中,json_decode($str, true)的第二个参数true表示返回数组,这通常比返回对象更安全(避免属性注入)。在JavaScript中,使用JSON.parse()而不是eval()

7. 常见问题与排查思路

在实际开发和测试中,你会遇到各种具体问题。下表汇总了典型场景:

问题现象可能原因排查方式解决方案
工具(如sqlmap)无法检测到JSON注入点1. 注入点不在JSON中。
2. 请求头Content-Type不是application/json
3. 工具有bug或版本旧。
4. 存在WAF拦截了探测请求。
1. 用Burp手动测试,确认参数位置。
2. 检查请求头是否正确。
3. 更新工具到最新版。
4. 尝试使用--tamper脚本绕过WAF。
1. 确保sqlmap命令正确使用-r--data指定了JSON数据。
2. 手动在Burp中构造简单载荷(如')测试。
后端代码使用了参数化查询,但测试仍报告漏洞1. 报告可能是误报(如静态扫描工具)。
2. 参数化使用不正确(如拼接了表名、列名)。
3. 存在其他非SQL的注入点(如命令、NoSQL)。
1. 人工审计代码,确认SQL拼接点。
2. 检查ORM查询中是否使用了字符串插值。
3. 全面检查所有使用JSON数据的地方。
1. 修复错误的参数化用法。
2. 对表名、列名等使用白名单校验。
JSON解析失败导致服务报错1. 客户端发送了畸形的JSON(如缺少引号)。
2. 字符编码问题。
3. 数据量过大。
1. 查看服务端错误日志。
2. 在代码中捕获JSON.parsejson_decode的异常。
1. 实现健壮的解析,对异常请求返回400错误。
2. 对请求体大小做限制。
防御代码已编写,但不确定是否全覆盖1. 代码审查遗漏了某些接口。
2. 第三方库或中间件引入了新的数据处理路径。
1. 使用SAST(静态应用安全测试)工具扫描代码。
2. 进行彻底的渗透测试,特别是针对API接口。
1. 建立代码安全审查流程。
2. 将安全测试纳入CI/CD流水线。

8. 最佳实践与工程建议

将安全融入开发流程,而不仅仅是事后补救。

  1. 安全左移:在需求设计和编码阶段就考虑安全。制定API安全规范,明确所有输入的验证规则。
  2. 使用安全的框架和库:现代Web框架(如Spring Security、Django、Laravel)都提供了强大的输入验证和防护机制。优先使用它们,而不是自己造轮子。
  3. 依赖项安全:定期使用npm auditpip-auditOWASP Dependency-Check等工具检查项目依赖的第三方库是否存在已知漏洞。
  4. 自动化安全测试
    • SAST:在代码提交阶段使用SonarQube、Fortify、Checkmarx等工具进行静态分析。
    • DAST:在测试环境使用OWASP ZAP、Burp Suite Professional进行动态扫描。
    • IAST:在运行阶段使用交互式扫描工具。
  5. 深度防御:不要只依赖一层防护。结合输入验证、参数化查询、输出编码、最小权限原则和WAF。
  6. 日志与监控:记录所有失败的验证尝试、异常的输入模式。设置告警,以便在遭受攻击时能快速响应。
  7. 定期安全培训:让开发团队了解最新的攻击手法和防御技术。JSON注入这类漏洞,根源往往是开发者的安全意识不足。

9. 总结与后续学习方向

JSON注入的本质是“数据格式安全”不等于“数据内容安全”。它提醒我们,在微服务和API驱动的架构下,接口安全需要格外的关注。

通过本文,你应该已经掌握了:

  • 攻击视角:如何使用Kali Linux下的工具(Burp Suite, sqlmap)来探测和利用JSON注入漏洞。
  • 防御视角:如何通过输入验证、参数化查询、避免危险函数等编码实践,从根本上杜绝此类漏洞。

要真正巩固这些知识,建议你:

  1. 动手实验:按照第3、4节的步骤,在DVWA或自己搭建的简单应用上,亲手完成一次从环境搭建到漏洞利用的完整过程。
  2. 代码审计:检查你正在维护的项目,搜索JSON.parsejson_decode@RequestBodyexpress.json()等关键字,然后跟踪这些数据的使用路径,看是否存在危险的拼接操作。
  3. 拓展学习:了解相关的漏洞类型,如XXE(XML外部实体注入)反序列化漏洞模板注入(SSTI)。它们与JSON注入有相似的逻辑——“解析”+“误用”。
  4. 关注工具更新:安全工具和攻击技术都在不断进化。定期关注sqlmap、Burp Suite、OWASP ZAP等工具的更新日志和新特性。

安全是一个持续的过程,而非一劳永逸的状态。希望这篇文章能成为你构建更安全Web应用的一块坚实基石。建议收藏本文,并在下次代码评审或设计API时,将其作为一份实用的安全检查清单。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询