堆叠注入依赖驱动层显式支持多语句执行,禁用mysqli_multi_query和pdo::mysql_attr_multi_statements才是根本防御,waf拦分号无效且易绕过。

禁用 MySQLi/PDO 的多语句执行能力
堆叠注入不是靠“绕过过滤”起效的,而是依赖驱动层显式支持分号分隔的多语句执行。只要底层不许执行,攻击者输再多分号也只会报错——mysqli_query() 遇到分号直接抛 MySQL syntax error,而 mysqli_multi_query() 和开启 PDO::MYSQL_ATTR_MULTI_STATEMENTS 的 PDO 才真正危险。
实操建议:
- MySQLi 连接时,**不要传
MYSQLI_CLIENT_MULTI_STATEMENTS标志**:$mysqli = new mysqli($host, $user, $pass, $db, $port);(第 6 个参数留空) - PDO 连接时,**必须显式关闭**:
$pdo = new PDO($dsn, $user, $pass, [PDO::MYSQL_ATTR_MULTI_STATEMENTS => false]); - 框架项目(如 Laravel)检查数据库配置中是否含
options => [PDO::MYSQL_ATTR_MULTI_STATEMENTS => true],删掉它
为什么 WAF 拦分号根本不可靠
WAF 层对分号(;)或 %3B 的拦截,只是表层过滤。攻击者可轻松绕过:用 URL 编码、空格混淆、注释符包裹(/*;*/),甚至走 CLI 脚本、定时任务、内部 RPC 等非 HTTP 接口直连数据库——这些路径完全绕开 WAF。
真正有效的防御点只有一个:在连接建立那一刻,就让驱动拒绝多语句能力。这是唯一能覆盖所有调用路径的防线。
禁用后哪些旧代码会直接失败
禁用多语句后,不是静默降级,而是立刻报错。典型现象包括:
mysqli_multi_query(): Unable to execute multiple queriesPDOException: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active
这类失败常见于以下场景:
- CMS 安装脚本(一次建表+插入+授权)
- 数据库迁移工具(如早期 Laravel Migrate 的 raw 批量执行)
- 老系统中的初始化逻辑(
CREATE TABLE; INSERT INTO; GRANT ...)
替代方案不是“恢复多语句”,而是拆成多次 mysqli_query() 或用事务包裹单条语句:BEGIN; INSERT ...; UPDATE ...; COMMIT;
参数化查询不能替代禁用多语句
参数化查询(mysqli_prepare() / PDO::prepare())只保护值,不保护语句结构。如果攻击者通过拼接构造出 SELECT ...; DROP TABLE users; 并交给 mysqli_multi_query(),参数化对第二条语句完全无效——它压根没参与预编译过程。
也就是说:prepare() 防不住堆叠,只有禁用多语句才能切断执行链路。两者是正交策略:参数化防数据污染,禁用多语句防语句分裂。
最容易被忽略的是:很多团队花大力气做输入校验、WAF 规则、ORM 升级,却没动数据库连接初始化那一行配置。只要 PDO::MYSQL_ATTR_MULTI_STATEMENTS 是 true,所有上层防护都形同虚设。











