预编译能彻底阻断UNION联合注入,因其将SQL结构与用户输入严格分离,使UNION等关键字无法进入语法解析阶段;而过滤、WAF等仅属补救措施,易被大小写混淆、内联注释、双写绕过等方式突破。

用预编译语句(PreparedStatement 或 PDO::prepare)能直接阻断联合查询型 SQL 注入,其他手段如过滤、WAF、输入校验都属于补救措施,无法替代它。
为什么 UNION SELECT 在预编译下完全失效
联合注入依赖于把攻击载荷拼进 SQL 字符串里,让数据库解析执行。而预编译把 SQL 结构和数据彻底分离:语句模板在服务端提前编译,用户输入只作为参数值传入,数据库引擎不会对参数做语法解析。
比如这句漏洞代码:SELECT * FROM users WHERE id = $_GET['id'],攻击者可构造 id=1 UNION SELECT username,password FROM users--;但换成预编译后:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
无论 $_GET['id'] 是 1 还是 1 UNION SELECT ...,最终只会被当作一个整数值代入,UNION 关键字根本不会进入 SQL 解析阶段。
哪些“看起来像防御”的做法实际无效
- 仅过滤单引号(
')或分号(;):攻击者可用%27(URL 编码)、/**/注释、或CHAR(39)绕过 - 用
mysql_real_escape_string(已废弃)或简单addslashes():在宽字节环境或特定编码下可能被绕过,且对数字型参数无意义 - 前端 JS 校验:完全可被绕过,不影响后端 SQL 构造
- WAF 规则拦截
UNION SELECT:容易被大小写变形(UnIoN SeLeCt)、内联注释(/*!50000union*/)、或分块传输绕过
必须同步做的三件事,否则预编译也白搭
预编译只是起点,不是终点。漏掉任意一项,仍可能被拖库或提权:
-
数据库账号权限最小化:应用连接数据库的账号不能有
information_schema读权限,更不能有FILE权限(防LOAD_FILE或INTO OUTFILE) -
关闭详细错误回显:生产环境必须关掉
display_errors,禁用mysqli_error()直接输出——否则即使注入失败,报错信息也可能泄露表结构或路径 - 所有参数都走绑定,不混用:常见坑是“大部分用了 ?,但排序字段或表名硬拼进去”,这类动态对象必须通过白名单控制,不能靠任何过滤
真正难的不是写对那一行 prepare,而是确保每个查询、每种参数类型、每处动态 SQL 片段,都被统一纳入参数化流程——漏掉一个点,就等于留了一扇没锁的门。











