jsonb字段本身不引入sql注入,但将解析后的值(如data->>'name')拼入sql字符串会导致严重注入风险;必须使用预编译参数绑定,禁用${}拼接,对order by、filter等动态结构实施白名单+正则双校验,并严格限制嵌套路径深度。

JSONB字段不能直接拼接进WHERE条件
PostgreSQL的jsonb类型本身不引入SQL注入,但常见错误是把解析后的值(比如data->>'name'结果)用+或String.format()塞进SQL字符串。一旦这么做,就等于放弃所有防御——数据库只认语法结构,不管你是从JSON里取的还是手写的。
典型错误现象:MySQLSyntaxErrorException频发但SQL日志看不出明显错;传{"name":"admin' -- "}后查出全部用户;日志里出现EXTRACTVALUE或UNION SELECT报错特征。
- 所有
JsonNode.asText()、map.get("name")、object.field的结果都必须视为不可信输入 - 哪怕字段名是
status、值看着像"active",也不能跳过校验 - 强类型绑定、trim、非空判断全无效——它们不影响SQL语法解析
ORDER BY和FILTER字段必须白名单硬校验
JSON里如果带{"sort":"email DESC"}这种字段,ORDER BY ?会直接报错:预编译不支持结构参数。这时候绝不能妥协用String.format("ORDER BY %s", sort)。
正确做法是白名单+正则双保险:
- 定义明确白名单:
Set.of("id ASC", "id DESC", "email ASC", "email DESC") - 方向单独校验:
if (!"ASC".equals(dir) && !"DESC".equals(dir)) throw new IllegalArgumentException(); - 完整匹配组合:
"email DESC"必须整体在白名单中,不能只校验email部分 - 拒绝任何空白符、Unicode分隔符、不可见字符——漏掉一个就可能被绕过
IN查询必须动态生成占位符
遇到{"ids":[1,2,3]}这类结构,不能写成"id IN (" + String.join(",", ids) + ")",也不能用MyBatis的${}拼接。
PostgreSQL支持= ANY(?)或ANY(ARRAY[?]),但参数必须是单个数组对象,不是字符串:
- MyBatis示例:
<foreach item="id" collection="filter.ids" open="id = ANY(ARRAY[" separator="," close="])">#{id}</foreach> - 原生JDBC需根据数组长度动态构建
?数量,再调用setArray()或setObject() - 手动拼接IN列表是高危行为,无论是否加引号或转义
MyBatis中JSONB字段必须用#{},禁用${}
${}是字符串替换,进去就是裸奔;#{}才走PreparedStatement预编译。哪怕你只拼一个字段名,只要用了${},整个查询就不可信。
优先用Jackson的@JsonProperty+POJO绑定,而非泛型Map<string object></string>——运行时类型模糊容易误判,比如把数字当字符串处理,导致后续类型转换异常或绕过校验。
复杂点在于嵌套路径校验:比如data->'user'->>'role'中的user和role都要进白名单,且路径深度需限制(如最多两层),否则可能被data->'a'->'b'->'c'->'d'->'e'这类长路径耗尽资源。











