ORDER BY 不能参数化是 SQL 标准限制,占位符仅支持值绑定;使用 ? 会导致字符串字面量排序失效,${} 是纯文本替换需白名单校验,导出场景更需严防注入。

ORDER BY 不能参数化,不是框架缺陷,是 SQL 标准和数据库引擎的硬性限制——占位符(? 或 #{})只能绑定“值”,不能绑定“标识符”,强行使用会报错或产生语义错误。
为什么 ORDER BY ? 会报错或失效
数据库在预编译阶段会自动给 ? 绑定的值加上单引号,变成字符串字面量。例如:ORDER BY ? 绑定 "username",实际执行的是 ORDER BY 'username',即按常量字符串排序,所有行视为同一值,失去列排序意义。
主流数据库行为一致:
- PostgreSQL 报错:
ERROR: syntax error at or near "$1" - MySQL 报错:
You have an error in your SQL syntax - SQL Server 报错:
Incorrect syntax near '@P1'
这不是 JDBC 或 MyBatis 的锅,而是 SQL 解析器在语法层就拒绝把占位符当标识符处理。
MyBatis 中 ${} 不等于安全拼接
${} 是纯文本替换,不做任何转义,等价于 Java 的 "ORDER BY " + field。常见错误写法包括:
-
ORDER BY ${sortField} ${sortOrder}:两个变量都来自请求,任一未校验即沦陷 -
ORDER BY ${colName} ASC, ${colName2} DESC:多字段需逐个白名单校验 -
ORDER BY (SELECT password FROM user LIMIT 1):攻击者可注入子查询、函数甚至延迟载荷
MyBatis-Plus 的 QueryWrapper.orderBy(true, true, "id; DROP TABLE users--") 同样危险——它内部仍是字符串拼接,不是预编译。
白名单校验必须前置且全覆盖
校验不能只靠正则或过滤单引号,必须基于有限、明确、大小写敏感的合法集合。关键动作:
- 定义静态白名单,如
Map.of("id_asc", "id ASC", "name_desc", "name DESC") - Controller 层收到
sort=id_desc后立即查表,不匹配则throw IllegalArgumentException,不进 Service - 禁止在 MyBatis XML 中出现裸
${sortField};必须用${safeSortClause},且该变量由 Service 赋值 - 若用多字段排序(如
sort=name,created_at),先split(",")再逐项校验,任一非法则拒
别信“前端传什么我就校验什么”的懒方案——攻击者可绕过前端直接调接口,校验必须落在服务端入口第一层。
导出功能中 ORDER BY 注入最易被忽略的点
导出场景下,ORDER BY 注入往往不报错、不中断流程,攻击者能静默探测表结构或提取数据。比如传 sort_field=(SELECT password FROM admins LIMIT 1),响应时间变长或返回异常值就是信号。
更隐蔽的风险:
- 反引号包裹字段名:
sort_field=`status`; DROP TABLE users--可绕过基础正则 - Unicode 空格、零宽字符用于混淆校验逻辑
- 别名注入:
user_name AS `status; DROP TABLE users;--`在 SELECT 子句中触发二次执行
真正安全的导出,不是“加了 WAF 就放心”,而是每个排序字段+方向组合都严格对应白名单中的一个键,且该映射不可被运行时修改。











