JSON解析后直接拼SQL等于放弃防御,所有解析结果必须视为不可信输入,严格白名单校验字段名与路径,并全程使用参数化查询(如#{}或?),禁用字符串拼接、${}和动态拼接IN列表。

JSON解析后直接拼SQL字符串等于放弃防御
只要把 JsonNode.asText()、map.get("name") 或 object.field 的结果用 + 或 String.format() 塞进 SQL 字符串,就立刻回到高危状态。强类型绑定、trim、非空校验全无效——数据库只认语法,不认“你本意是不是想查数据”。
常见错误现象包括:MySQLSyntaxErrorException 频发但 SQL 日志看不出明显语法错;传 {"status":"active' -- "} 后查询返回全部记录;后台日志出现 UNION SELECT 或 EXTRACTVALUE 等报错注入特征。
- 所有 JSON 解析结果都必须视为不可信输入,哪怕字段名是
status、值看起来只该是"active"或"inactive" - 优先用 Jackson 的
@JsonProperty+ POJO 绑定,而非泛型Map,减少运行时类型模糊带来的误判 - MyBatis 中 JSON 字段必须用
#{},禁用${}——${}是字符串替换,进去就是裸奔;#{}才走PreparedStatement预编译
动态字段名和JSON路径必须白名单硬校验
JSON 里如果带 {"sort":"email DESC"} 这种字段,ORDER BY ? 会直接报错——预编译不支持结构参数。这时候不能妥协用 String.format("ORDER BY %s", sort),得靠白名单+正则双保险。
性能影响几乎为零,但漏掉一个字符(比如空白符、Unicode 分隔符、不可见字符)就可能被绕过。
- 定义明确白名单:
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 示例:,最终生成 id IN (?, ?, ?) 并绑定三个参数。
- 手动拼接
IN列表是高危行为,无论是否加引号或转义 - PostgreSQL 使用
ANY(ARRAY[?])或= ANY(?)时,参数仍需是单个数组对象,不是字符串 - 若用原生 JDBC,需根据数组长度动态构建
?占位符串,并逐个setLong(i, id)
JSONB字段查询不能直接拼接路径表达式
PostgreSQL 中禁止这样写:WHERE content->>'" + userInputPath + "'='" + userInputValue + "'"。JSON 路径本身来自用户输入,拼进去就等于把控制权交给攻击者。
正确做法是:对允许的 JSON 路径做白名单限制(如只允许 ["title", "author", "tags"]),再配合参数化处理值部分。
- 路径名必须白名单校验,不能只校验内容是否为字符串
- 值部分一律用
#{}或?占位符,例如content->>>'title' = ? - 若需模糊匹配,用
content->>'title' ILIKE concat('%', ?, '%'),而非"%${value}%" - 避免使用
jsonb_path_exists(content, '$.??(@.key == "xxx")')这类可执行路径表达式的函数,除非路径字符串完全由服务端控制
JSON 字段解析后不做白名单校验、不走参数化,哪怕只漏一处,就足以让整条查询链路失效。最危险的不是“不知道怎么防”,而是“以为已经防住了”。











