order by无法参数化是因为sql语法要求其后字段名和排序方向不能加引号,而预编译机制(如#{})会自动添加引号导致语法错误,故必须用${}拼接,但需严格白名单校验每个变量。

因为 ORDER BY 后面不能用 #{} 参数化,必须拼接字符串,而开发者常误以为“只传字段名就安全”,放任用户输入直接进入 SQL 流程。
ORDER BY 为什么无法参数化?
主流数据库(MySQL、PostgreSQL 等)不支持在 ORDER BY 后使用占位符(如 ? 或 $1)替代列名或排序方向。这不是 MyBatis 或框架的限制,是 JDBC 和 SQL 语法层的硬性约束:
- 写成
ORDER BY ?并绑定"username",数据库会按字符串字面值'username'排序(即所有行都排在同一位置),不是按username列的值排序 -
ORDER BY ${sortField}看似只是“动态替换”,但实际等价于字符串拼接,MyBatis 不做任何转义或拦截 - MyBatis-Plus 的
orderByAsc(String column)方法内部仍是字符串拼接,不是预编译——传入"id; DROP TABLE user--"会原样出现在最终 SQL 中
哪些输入点真正危险?
危险不在“有没有用 <if></if>”,而在“${} 里是否包裹了未经校验的用户输入”。常见高危组合:
一款AI工具,主要用于通过后台进程运行 Codex CLI、Claude Code、OpenCode 或 Pi Coding Agent,实现程序化控制,适合需要提升相关任务效率的用户。
-
ORDER BY ${sortField} ${sortOrder}:两个变量都来自请求参数,任意一个未校验即失守 -
ORDER BY (SELECT ...)类表达式:攻击者可构造IF(1=1, id, email)或SLEEP(5)控制排序逻辑或触发延迟 -
ORDER BY ${colName} ASC, ${colName2} DESC:多字段排序时,每个${}都需独立白名单校验 - 前端传
sort=name%20ASC%2C%20(SELECT%20password%20FROM%20user%20LIMIT%201),后端未 decode + 校验,直接拼入
白名单校验为什么必须在 XML 里做?
Java 层校验(如 Controller 里 if (!validFields.contains(sort)) throw ...)可能被绕过:反射调用、中间件篡改、多线程竞争、或框架自动绑定跳过校验逻辑。而 <if test="sortField == 'name' || sortField == 'created_time'"></if> 是 MyBatis 运行时 OGNL 表达式,在 SQL 组装前执行,天然拦截非法值进入拼接阶段:
- 只允许枚举值:
test="sortField == 'id' || sortField == 'username' || sortField == 'email'" - 排序方向也需联合校验:
test="sortField == 'name' && sortOrder == 'asc'",不能只校验字段名 - 避免
<otherwise>ORDER BY ${sortField}</otherwise>—— 这等于把校验逻辑全废掉 - 正则校验不够:比如
^[a-zA-Z0-9_]+$无法防住"id ASC, (SELECT 1)",必须精确匹配合法字段+方向组合
最易被忽略的是:ORDER BY 注入往往不报错、不中断流程,攻击者能静默提取数据或探测结构。真正防线不是“能不能拼”,而是“拼之前有没有把每个 ${} 都钉死在白名单里”。










