union 是合法 sql 操作符,攻击者利用其正常性将恶意逻辑藏于合法结构中,通过列数探测和回显位定位实施注入,而参数化查询可从根本上阻止其进入 sql 解析阶段。

UNION 是合法 SQL 操作符,不是“危险指令”
数据库解析器不把 UNION 当作攻击信号,它和 SELECT、FROM 一样是标准语法成分。WAF 或正则黑名单拦 SELECT 或 ;,但不会拦 UNION——因为很多正常业务查询真会用到它(比如报表合并)。攻击者正是利用这点,把恶意逻辑藏在“合法结构”里。
常见错误现象:页面对 id=1; DROP TABLE users 返回 403,但 id=1 UNION SELECT 1,2,3 却成功返回数字,后台日志里甚至没有告警。
大小写、注释、编码让正则匹配彻底失效
正则过滤依赖字符串层面的字面匹配,而数据库执行的是语法树。只要最终能被解析成有效 UNION SELECT 结构,中间怎么变形都行:
-
UnIoN/**/SeLeCt—— 大小写混用 + 注释打断,/union\s+select/i可能漏掉空格匹配 -
1%20UNION%20SELECT%201—— URL 编码空格,过滤器若在解码前扫描,看到的是%20UNION%20,不匹配关键词 -
1/*a*/UNION/*b*/SELECT/*c*/1—— 注释切割,人眼可读,正则难覆盖所有分割组合
更隐蔽的是 Unicode 零宽字符或 Tab(%09)替代空格,这些在原始请求体里根本不像“攻击特征”。
攻击者不靠“触发关键字”,而靠试探数据库行为
真正让 UNION 注入落地的,不是它含不含敏感词,而是它能触发数据库两个硬性反馈:
- 用
ORDER BY 1→ORDER BY 2→ … 直到报错ERROR 1222,反推出原始查询列数 - 用
UNION SELECT 1,2,3,...N验证哪一列被渲染到页面,从而定位回显位
这些试探载荷本身可能完全不带 SELECT 或 UNION 字样(比如只用 ORDER BY),正则规则根本没机会介入;而一旦列数摸清,后续 UNION SELECT user(), database() 就只是“填空”,不再需要绕过关键词检测。
参数化查询让 UNION 根本无法进入 SQL 解析阶段
防御的关键不是“怎么拦住 UNION”,而是让它压根没机会成为 SQL 的一部分。参数化查询(如 PDO::prepare()、mysqli->prepare())把用户输入当作纯值传入,数据库收到的是已编译好的执行计划 + 数据,' UNION SELECT ... 这串字符只会被当做一个字符串字面量处理,不会参与语法分析。
容易被忽略的一点:哪怕你对输入做了 intval() 或 is_numeric(),只要 SQL 是拼出来的("SELECT * FROM news WHERE id = " . $id),1 UNION SELECT 这类载荷依然生效——因为数字 1 后面紧跟着空格和 UNION,拼接后就是完整恶意语句。










