ORDER BY后的列名和排序方向无法参数化,必须通过白名单校验并配合数据库特定的标识符转义(如MySQL用反引号、SQL Server用QUOTENAME)来防止SQL注入。

ORDER BY 后的列名不能用参数化查询
这是最常被误解的一点:数据库不支持把 ORDER BY 后的列名或排序方向(ASC/DESC)当作参数绑定。你写 ORDER BY ? 或 ORDER BY @col,绝大多数数据库会直接报错——因为列名属于 SQL 语法结构,不是“值”。硬塞进参数占位符只会触发语法错误,而不是安全执行。
白名单校验是唯一可靠方案
对用户传入的排序字段(如 $_GET['sort'] 或 @sort_column),必须严格限制为预定义的合法值。这不是“建议”,而是强制要求。
- 明确列出所有允许的列名,比如
['id', 'name', 'created_at', 'status'],其余一概拒绝 - 排序方向必须单独校验,只接受
'ASC'或'DESC',且转为大写后再比对(避免大小写绕过) - 不要用
in_array($input, $whitelist, true)就完事——确保输入未被截断、未含不可见字符(如零宽空格、BOM) - 若需支持多字段排序(如
name,created_at DESC),应拆解后逐字段白名单校验,再拼接,而非整段放行
拼接前必须用 quotename 或 identifier escape
即使通过了白名单,拼接到 SQL 中时仍可能因特殊字符(如带空格的列名 [user name] 或含中划线的 first-name)引发语法错误或注入风险。不同数据库 escape 方式不同:
- SQL Server:用
QUOTENAME(@col)包裹列名,它自动加[]并转义内部] - PostgreSQL:用双引号
"col_name",但需手动替换双引号为两个双引号("→"") - MySQL:用反引号
`col_name`,注意不能直接拼接用户输入,必须先过滤非法字符(如反引号本身) - PHP 中若用 PDO,没有内置 identifier escape 函数,得自己实现或依赖 DBAL 类库
别信“过滤关键词”或“正则黑名单”
试图靠 str_replace([';', '--', '/*'], '', $input) 或正则匹配 /union|select|drop/i 来防排序注入,基本无效。
- 攻击者可用 Unicode 同形字(如全角
ASC)、嵌套注释(/*/**/ORDER)、大小写混用(OrDeR)绕过 - 哪怕过滤掉所有关键字,只要列名拼接未 escape,仍可能因注入式标识符(如
name]; DROP TABLE users--)触发语句截断或执行异常 - 这类过滤还容易误杀合法字段名(如列名含
user_id,被误判为user关键词)
白名单 + 正确 escape 是唯一路径。漏掉任一环节,都等于在 ORDER BY 后留了一扇没锁的门。


















