json解析后禁止拼接sql字符串,必须用预编译参数(如mybatis的#{})和硬白名单校验动态字段、json路径、排序方向及in列表,杜绝${}、string.format或+拼接。

JSON解析后别直接拼SQL字符串
只要把 JsonNode.asText()、map.get("name") 或 user.getName() 的结果用 + 或 String.format() 塞进 SQL 字符串,就等于放弃所有防御。数据库不认“你本意”,只认语法——哪怕值看着像 "active",传进来 "active' -- " 就能绕过所有 trim、非空、类型校验。
- 常见错误现象:
MySQLSyntaxErrorException频发但日志里看不出明显语法错;传{"status":"active' -- "}后查出全表数据;后台日志出现UNION SELECT或EXTRACTVALUE报错特征 - 强类型绑定(如
@RequestBody User user)本身不防注入,它只确保结构可控;真正起作用的是后续是否用#{id}而不是${id} - MyBatis 中必须用
#{},禁用${}——后者是字符串替换,进去就是裸奔;#{}才走PreparedStatement预编译
动态字段名和JSON路径必须硬白名单校验
JSON 里如果带 {"sort":"email DESC"} 这种字段,ORDER BY ? 会直接报错——预编译不支持结构参数。这时候不能妥协写 String.format("ORDER BY %s", sort),得靠白名单+正则双保险。
- 定义明确白名单:
Set.of("id", "name", "email", "created_at") - 方向单独校验:
if (!"ASC".equals(dir) && !"DESC".equals(dir)) throw new IllegalArgumentException(); - 完整匹配组合:
"email DESC"必须整体在白名单中,不能只校验email部分 - 拒绝任何空白符、Unicode 分隔符、不可见字符——漏掉一个就可能被绕过
JSON嵌套数组要转成IN (?, ?, ?)形式
遇到 {"ids":[1,2,3]} 这类结构,不能写成 "id IN (" + String.join(",", ids) + ")",也不能用 ${} 拼接。必须让 ORM 动态生成占位符数量。
- MyBatis 示例:
<foreach item="id" collection="filter.ids" open="id IN (" separator="," close=")">#{id}</foreach>,最终生成id IN (?, ?, ?)并绑定三个参数 - 手动拼接 IN 列表是高危行为,无论是否加引号或转义
- PostgreSQL 使用
ANY(ARRAY[?])时,参数仍需是单个数组对象,不是字符串;原生 JDBC 需根据数组长度动态构建?占位符串,并逐个setLong(i, id)
JSONB字段查询不能直接拼接路径
PostgreSQL 中禁止这样写:WHERE content->>'" + userInputPath + "'='" + userInputValue + "'。JSON 路径本身来自用户输入,必须视为不可信输入。
- 对允许的 JSON 路径做白名单限制,如只允许
["title", "author", "tags"] - 若路径不确定,改用
jsonb_path_exists(content, '$.title == $1')类函数,把值作为参数传入 - 避免使用
->>操作符拼接用户控制的路径字符串,这是最常被忽略的盲点
JSON字段解析后的值、路径、排序字段、IN列表,全都不该出现在字符串拼接里。最容易被忽略的是路径拼接和 ORDER BY 动态字段——它们没法走 #{},只能靠白名单兜底,而白名单一旦漏字符或没校验空白符,防线就垮了。











