【SQL注入系列】JSON注入
什么是JSON?
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于 ECMAScript 规范的一个子集,但采用独立于编程语言的文本格式来存储和表示数据。它易于人阅读和编写,同时也易于机器解析和生成,常用于Web前端与后端服务器之间的数据传输。声明:本篇内容基于网络安全知识体系,所学用途皆不可用于法律之外的攻击与入侵
什么是JSON注入?
JSON注入是一种安全漏洞,当应用程序将用户输入未经安全处理就直接拼接到一个JSON数据结构或JSON字符串中时,攻击者可以通过构造特殊的输入来破坏JSON的结构,从而注入恶意代码或改变数据的语义。
JSON注入的常见场景
场景:主要发生在Web应用程序前后端通过JSON格式进行数据交互时。例如:后端根据用户请求,动态生成一段JSON数据返回给前端。前端将用户输入的数据以JSON格式提交给后端,后端直接解析并使用(例如用于数据库查询)。
注入恶意数据:通过破坏结构,攻击者可以插入任意JSON数据,这些数据可能会被后端程序信任并使用,从而导致其他安全问题(如XSS、权限绕过等)。
与SQL注入结合:在某些情况下,后端程序可能会将JSON数据中的某个值取出,并不加过滤地拼接到SQL查询语句中。这就导致了“JSON注入”最终引发了“SQL注入”。
攻击方式:破坏JSON结构:如果用户输入的值中包含未转义的双引号 "
或花括号 }
等特殊字符,攻击者可以提前终止字符串或键值对,并注入新的键值对。
前端与后端API交互时的核心数据转换过程:接收到的JSON字符串 → 转换为对象进行操作 → 处理完毕后可能再转换为字符串发送回去。下图是json的一般格式。
JSON.parse()
: 用于将一个 JSON 字符串转换为 JavaScript 对象。
JSON.stringify()
: 用于将 JavaScript 对象转换为 JSON 字符串。
Object.keys()
: 返回一个给定对象自身的所有可枚举属性名的数组。
.forEach()
: 数组的迭代方法,用于遍历数组的每个元素。
接下来进入到实战模式,首先源代码必须放在php/www文件下。新建文本文件,把代码放进去,并把文件扩展名改为.php
打开火狐,输入网址http://localhost/json1.php,按f12打开开发者模式,点击hackbar。输入:json={"username":"admin"} 点击execute显示出用户名和密码。
然后用单引号闭合,添加and,返回结果
由于 and 1=2
导致查询条件永远不成立,数据库返回了空结果集。后端程序拿到空结果后,就在页面上显示了“查询结果为空”。
如果系统是安全的:特殊输入会被转义或拒绝,页面可能会返回错误,或者统一返回“登录失败”等模糊信息。现在系统返回“查询结果为空”:这清晰地表明注入的SQL代码 and 1=2#
被数据库成功执行了,您已经能够通过输入来控制数据库的查询逻辑。
输入:json={"username":"admin' and 1=2 union select 1,2#"}
admin'
:用于闭合SQL语句中字符串的引号。
and 1=2
:制造一个永假条件,使得原本查询用户 admin
的部分失败并返回空结果。
union select 1,2
:这是攻击的核心。UNION
操作符用于合并两个SELECT语句的结果集。select 1,2
是攻击者自定义的查询,目的是探测数据库返回结果的位置(即页面的哪个部分会显示查询结果的第一列和第二列)。
#
:注释符,用于注释掉后续所有SQL代码,避免语法错误。
这证明攻击成功。数字 1
和 2
来自攻击者自定义的 union select 1,2
。这个结果清晰地指明了回显位:查询结果的第一列(1
)的值会显示在“用户名”的位置,第二列(2
)的值会显示在“密码”的位置。
接下来
获取真实信息:将 1, 2
替换为数据库函数,从而窃取敏感信息。例如:
{"username":"admin' and 1=2 union select database(), version()#"}
返回 用户名:数据库名,密码:数据库版本
。
窃取所有数据:在摸清数据库结构(表名、列名)后,构造Payload直接dump出所有用户和密码:
{"username":"admin' and 1=2 union select group_concat(username), group_concat(password) from users#"}
此处看到用户名和密码已经全部获取
如何防御?根本的解决方案是:使用参数化查询(预编译语句)。
以PHP(PDO)为例,安全的写法应该是:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username"); $stmt->execute(['username' => $username]); // 用户输入会被安全地处理为数据,而非代码 $results = $stmt->fetchAll();
这种方法从原理上杜绝了SQL注入的可能。
感谢大家的观看,小编呆呆羊与大家一同学习一同成长