SQL注入的核心在于欺骗数据库执行非预期的SQL语句,而JSON类型字段的无引号注入则是一种针对现代数据库(如MySQL 5.7+、PostgreSQL等)中JSON数据类型的新型攻击手法。传统SQL注入依赖于破坏SQL语句结构,常需闭合单引号或双引号。但当数据库字段被定义为JSON类型,且应用程序直接存储或查询JSON字符串时,攻击者可以利用JSON语法本身进行注入,无需触碰SQL语句的引号边界。这是因为JSON数据在SQL中可能被视为一个字符串整体,但其内部结构可以被恶意操纵,从而影响SQL查询的逻辑,甚至执行任意代码。关键在于,许多开发者错误地认为“既然用了JSON格式,且参数被整体传入,就安全了”,却忽略了JSON内容本身可以成为注入载体。

JSON类型字段与传统字符串字段的安全差异

在传统SQL注入中,攻击目标通常是字符串字段,如"username"或"email"。攻击者通过输入"admin' OR '1'='1"来破坏引号闭合,改变WHERE子句逻辑。但当字段类型为JSON时,情况不同。例如,一个用户配置信息以JSON格式存储在"user_settings"字段中,应用程序可能这样查询:"SELECT * FROM users WHERE user_settings = '{"theme":"dark"}';"。这里,整个JSON对象作为一个字符串值被比较。然而,如果应用程序是动态构建JSON字符串进行查询,如"SELECT * FROM users WHERE user_settings = '{"theme":"' + user_input + '"}';",那么攻击者输入"dark"}'"可以提前闭合JSON对象,并添加额外SQL代码。但更隐蔽的风险在于,JSON类型字段常与数据库的JSON函数一起使用,如MySQL的"JSON_EXTRACT()"或"JSON_SEARCH()",这些函数解析JSON内容并返回结果,若解析过程被恶意输入干扰,可能导致注入。

无引号注入的原理与具体攻击向量

无引号注入之所以可行,是因为JSON语法中双引号用于键和值,而SQL语句中JSON字符串通常被单引号包裹。攻击者无需闭合外层SQL单引号,只需在JSON内部构造恶意结构。考虑一个场景:应用程序使用JSON字段存储搜索过滤器,后端代码将用户输入嵌入JSON,然后使用"JSON_EXTRACT()"函数查询。例如,查询语句为:"SELECT * FROM products WHERE JSON_EXTRACT(filter, '$.category') = 'electronics';",其中"filter"字段存储JSON如"{"category":"electronics","price_range":"100-200"}"。如果应用程序动态构建JSON路径,如"JSON_EXTRACT(filter, '$." + user_input + "')",攻击者输入"category') OR JSON_EXTRACT(filter, '$.price_range') = '100-200' -- ",会导致路径参数变为"$."category') OR JSON_EXTRACT(filter, '$.price_range') = '100-200' -- "",从而改变查询逻辑。这里,攻击者利用了JSON路径语法中的双引号,以及SQL注释符"--"来终止后续语句,无需触碰外层单引号。

另一个常见向量是JSON数组或对象注入。假设应用程序允许用户通过JSON数组选择多个选项,查询类似:"SELECT * FROM orders WHERE JSON_CONTAINS(products, '["laptop","mouse"]');"。如果用户输入被直接拼接进JSON数组字符串,攻击者可输入""laptop","mouse"]'); DELETE FROM orders; -- ",导致数组变为"["laptop","mouse"]'); DELETE FROM orders; -- "]",从而执行删除操作。这里,攻击者闭合了JSON数组的方括号,然后添加独立SQL语句,由于JSON字符串仍被外层单引号包裹,数据库会将其视为一个长字符串,但执行时,整个字符串被解析为SQL,造成注入。

利用JSON函数进行布尔盲注和数据窃取

对于更安全的应用程序,可能使用参数化查询,但若JSON函数参数动态构建,仍存在风险。例如,MySQL的"JSON_SEARCH()"函数用于查找JSON中的值路径,语句如:"SELECT JSON_SEARCH(filter, 'one', 'electronics') FROM products;"。如果搜索值来自用户输入,且未过滤,攻击者可输入"electronics' OR JSON_SEARCH(filter, 'all', '1') IS NOT NULL -- ",这会使函数参数变为"'electronics' OR JSON_SEARCH(filter, 'all', '1') IS NOT NULL -- '",导致函数执行异常,并通过错误响应或延迟推断数据。这种注入可用于布尔盲注,逐步提取JSON字段中的敏感信息。

此外,JSON类型字段常与"JSON_KEYS()"、"JSON_VALUES()"等函数结合,这些函数返回结果可作为子查询部分。攻击者可能注入子查询来窃取数据。例如,原始查询:"SELECT * FROM users WHERE JSON_EXTRACT(settings, '$.admin') = 'true';",若"admin"键值由用户控制,输入"true' AND (SELECT COUNT(*) FROM sensitive_table) > 0 -- ",可探测其他表存在。由于JSON值比较是字符串比较,注入的SQL片段会被执行。

防御策略:输入验证、参数化查询与最小权限

首先,输入验证必须针对JSON语法进行。不仅检查SQL关键字,还需验证JSON结构完整性。例如,确保用户输入不包含未闭合的双引号或方括号。对于JSON路径参数,应限制为允许的键名列表,避免动态拼接。使用白名单验证是最有效方法。

其次,参数化查询(预编译语句)仍是黄金标准。即使处理JSON字段,也应将整个JSON值作为参数传递,而不是拼接。例如,在Java中:"PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE user_settings = ?"); ps.setString(1, jsonInput);"。这确保JSON字符串被整体处理,数据库不会解析其内部为SQL。对于JSON函数参数,如"JSON_EXTRACT()"的路径,也应参数化:"JSON_EXTRACT(filter, ?)",路径作为单独参数传递。

第三,数据库权限最小化。应用程序数据库用户不应拥有执行"DELETE"、"DROP"等高风险命令的权限。限制JSON函数的使用范围,如禁用"JSON_SET()"等可能修改数据的函数在查询中。

第四,使用ORM框架时,注意其JSON序列化行为。许多ORM自动将对象转为JSON存储,但若允许用户提供完整JSON字符串,需严格验证。建议在业务层构造JSON对象,而非接收原始JSON输入。

第五,日志与监控。记录所有JSON相关查询的输入,特别是动态构建的部分。监控异常JSON结构,如包含SQL关键字或异常闭合符的JSON。

代码示例:安全与不安全实践对比

不安全代码(PHP示例):

$userInput = $_POST['category'];
$sql = "SELECT * FROM products WHERE JSON_EXTRACT(filter, '$.\"$userInput\"') = 'electronics'";
$result = mysqli_query($conn, $sql);

这里,用户输入直接嵌入JSON路径,若输入为"category') OR 1=1 -- ",路径变为"$."category') OR 1=1 -- "",导致注入。

安全代码(使用参数化查询):

$userInput = $_POST['category'];
$sql = "SELECT * FROM products WHERE JSON_EXTRACT(filter, ?) = 'electronics'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", "$.\"$userInput\"");
$stmt->execute();

注意,即使路径参数化,仍建议对"$userInput"进行白名单验证,确保其为合法键名。

结论:JSON字段并非注入免疫

JSON类型字段的引入本为增强数据灵活性,却带来了新的安全盲点。无引号注入利用了JSON与SQL语法的交互点,绕过传统引号过滤。防御需多层次:从严格的输入验证(针对JSON结构)、全面的参数化查询、到数据库权限控制。开发者必须意识到,任何数据格式,当与SQL动态结合时,都可能成为注入载体。安全实践应始终将用户输入视为敌对,无论其格式如何。随着JSON在数据库中更广泛应用,此类攻击将更频繁,提前加固代码是唯一出路。