关键在于解析后的值是否被拼入SQL字符串;若用+、String.format()、${}或jsonNode.asText()直接拼接,即存在SQL注入风险,必须改用#{}参数化并严格白名单校验字段名与路径。

直接检测 JSON 字段查询中的 SQL 注入,关键不是看字段名是否叫 json,而是看「解析后的值有没有被拼进 SQL 字符串」。只要用了 +、String.format()、${} 或 jsonNode.asText() 后直接塞进 SQL,就已经在裸奔。
怎么确认后端在 JSON 解析后拼 SQL?
常见错误现象包括:
-
MySQLSyntaxErrorException频发,但 SQL 日志里看不出明显语法错(比如没多出单引号) - 传
{"status":"active' -- "}后,本该只查 active 的记录,结果返回了全部数据 - 后台日志出现
UNION SELECT、EXTRACTVALUE等报错注入特征 - 接口对
{"sort":"email DESC"}这类字段不做校验,直接拼进ORDER BY email DESC
这些都说明:JSON 已被解析成字符串/对象,但没走参数化流程,而是用字符串拼接进了 SQL。
MyBatis 中 JSON 字段必须用 #{},禁用 ${}
${} 是字符串替换,不经过 PreparedStatement 预编译,任何内容原样进 SQL —— 即使你 trim()、非空校验、类型转成 int,也毫无意义。
正确做法是:
- 所有 JSON 解析结果(如
map.get("name")、jsonNode.get("email").asText())必须作为参数传给#{} - 如果字段名或排序方向来自 JSON(如
{"sort":"created_at", "dir":"DESC"}),不能写成ORDER BY ${sort} ${dir} - 必须白名单硬校验:
Set.of("id", "name", "email", "created_at")+if (!"ASC".equals(dir) && !"DESC".equals(dir)) throw new IllegalArgumentException() - 组合值(如
"created_at DESC")要整体匹配白名单,不能只校验字段名部分
JSON 嵌套数组怎么安全转成 IN (?, ?, ?)?
遇到 {"ids":[1,2,3]},绝不能:
"id IN (" + String.join(",", ids) + ")""id IN ${ids}"id IN (" + ids.stream().map(Object::toString).collect(Collectors.joining(",")) + ")"
正确方式取决于 ORM:
- MyBatis:
<foreach item="id" collection="filter.ids" open="id IN (" separator="," close=")">#{id}</foreach>,会动态生成id IN (?, ?, ?)并绑定三个参数 - 原生 JDBC:根据数组长度构建等量
?占位符,再循环调用ps.setLong(i, id) - PostgreSQL:若用
ANY(ARRAY[?]),参数必须是 Java 数组对象(如new Long[]{1L, 2L, 3L}),不是拼出来的字符串
JSON 字段本身不是漏洞,解析后信任它才是。最容易被忽略的点是:ORDER BY / GROUP BY / LIMIT 的参数无法用 #{} 绑定,必须靠白名单+正则双保险,且要拒绝所有空白符、Unicode 分隔符、不可见字符 —— 少一个校验,就可能被绕过。


















