json解析后必须走参数化查询,禁用字符串拼接;动态字段名和排序需白名单校验;json数组转in列表须动态占位符;优先使用强类型dto绑定而非泛型map。

直接拼接 JsonNode.asText()、map.get("field") 或对象属性值进 SQL 字符串,就等于主动关闭所有防护——无论字段名多“干净”,只要没走参数化查询,就是高危状态。
JSON 解析后必须走参数化查询,不能字符串拼接
常见错误是:先 json_decode($json, true) 得到数组,再用 "INSERT INTO t (name) VALUES('".$row["name"]."')" 拼 SQL。一旦 $row["name"] 是 "O'Reilly",SQL 就崩了,且可被注入。
- PHP 中必须用 PDO 或 MySQLi 的预处理:
$stmt = $pdo->prepare("INSERT INTO t (name) VALUES (?)"); $stmt->execute([$row["name"]]); - Java Jackson + MyBatis 场景下,
#{}是唯一安全写法;${}是硬编码替换,禁用 - 即使做了
trim()、is_string()、strlen() ,也挡不住 <code>"active' OR 1=1 -- "这类 payload
动态字段名和排序方向必须白名单校验
当 JSON 包含 {"sort": "email DESC", "filter": {"status": "active"}} 时,ORDER BY ? 会报错——预编译不支持结构化参数,但也不能退化为 "ORDER BY ".$sort。
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
- 字段名白名单只允许:
Set.of("id", "email", "created_at", "status") - 排序方向必须完整匹配:
"email DESC"要整体在白名单里,不能只校验"email"部分 - 拒绝所有空白符和 Unicode 分隔符(如
\u200b),正则建议用^[a-zA-Z_][a-zA-Z0-9_]*(\s+(ASC|DESC))?$并.trim()后全匹配
JSON 数组转 IN 列表必须动态占位符
遇到 {"ids": [1,2,3]},不能 "id IN (" . implode(",", $ids) . ")",也不能用 ${ids} 拼接——这等于把攻击面直接暴露给数组长度和内容。
- MyBatis 推荐用:
<foreach item="id" collection="filter.ids" open="id IN (" separator="," close=")">#{id}</foreach> - PDO 中需手动构建占位符:
$placeholders = str_repeat('?,', count($ids) - 1) . '?'; $sql = "SELECT * FROM t WHERE id IN ($placeholders)"; $stmt = $pdo->prepare($sql); $stmt->execute($ids); - PostgreSQL 若用
ANY(ARRAY[?]),参数必须传一个 PHP 数组,不是字符串;否则仍可能被绕过
强类型绑定优于泛型 Map 解析
用 ObjectMapper.readValue(json, MyDto.class) 比 ObjectMapper.readValue(json, Map.class) 更安全,因为字段名、类型、嵌套结构在编译期/加载期就固定,减少运行时误判。
- DTO 类加
@JsonProperty显式声明字段,避免map.get("user_name")这类易错键名 - 对 JSON 中的数字字段,如果业务只要整数,就用
Integer接收,Jackson 会自动拒绝"123.45"或"abc",省去手动类型校验 - 嵌套 JSON 如
{"profile": {"age": 25}},直接绑定到Profile profile字段,比层层map.get("profile").get("age")更可控
最易被忽略的是:JSON 路径校验和字段名白名单必须覆盖全部动态点,包括嵌套层级中的 key 名、排序字段、GROUP BY 字段、甚至 LIKE 模糊前缀——漏掉任意一处,都可能成为注入入口。










