allowmultiqueries=true是高危开关,因它使分号被解析为语句分隔符,导致注入payload可执行多条sql(如select+drop+into outfile),将单表读取升级为全库拖库。

多语句查询本身不是漏洞,但它是 SQL 注入从“单表读取”升级为“全库拖库”的关键跳板——只要数据库驱动和连接配置允许,攻击者一条注入 payload 就能 SELECT 出数据、UNION 拼接敏感表、再用 INTO OUTFILE 直接写文件导出,甚至执行 DROP DATABASE。
为什么 allowMultiQueries=true 是高危开关
MySQL JDBC 驱动默认禁用多语句,但很多老项目或调试环境会显式开启:jdbc:mysql://localhost:3306/db?allowMultiQueries=true。一旦开启,用户输入的 ; 不再被当作普通字符,而是被解析为语句分隔符。
- 正常逻辑:用户搜
iphone→ 执行SELECT * FROM products WHERE name LIKE '%iphone%' - 注入后:用户搜
iphone'; DROP TABLE users; --→ 实际执行三条语句:SELECT ...+DROP TABLE users+ 注释掉剩余逻辑 - 更危险的是:配合
INTO OUTFILE,攻击者可把整张表导出到 Web 可访问路径,比如SELECT * FROM users INTO OUTFILE '/var/www/html/dump.csv'
不同语言/驱动的多语句控制方式
不是所有客户端都支持多语句,也不是开了就一定危险,关键看底层驱动是否解析 ; 并交给服务端执行。
- Java(JDBC):
allowMultiQueries=true是显式开关,必须手动启用;MySQL 8.0+ 默认仍为false - PHP(mysqli):
mysqli_multi_query()是独立函数,普通mysqli_query()即使传入分号也只执行第一条 - Python(PyMySQL):
cursor.execute()默认不允许多语句;需设cursor = conn.cursor(multi=True)才能调用nextset()切换结果集 - Node.js(mysql2):
multipleStatements: true必须在连接配置中声明,否则;会被当作文本内容处理
禁用后,注入还能拖库吗
能,但难度陡增、路径受限。禁用多语句 ≠ 防住 SQL 注入,只是砍掉了最暴力的“一击脱库”能力。
- 报错注入(如
updatexml())、布尔盲注、时间盲注依然有效,只是只能逐字段、逐行猜解,耗时且易被 WAF 拦截 -
UNION SELECT仍可用,但要求前后查询字段数一致、类型兼容,且无法跨库查系统表(除非账号有information_schema权限) -
LOAD_FILE()和INTO OUTFILE被彻底禁用——这两个函数依赖服务端文件操作权限,而多语句是它们落地的常用载体 - 真实案例中,某后台管理接口虽存在注入,但因连接串未配
allowMultiQueries=true,攻击者最终只导出了当前业务表的几百条记录,而非整个information_schema或其他库
检查与修复的实操步骤
别只改代码,要确认连接层、驱动层、服务端三层是否真正切断多语句通路。
- 查应用连接字符串:grep -r "allowMultiQueries" ./src/;检查是否硬编码为
true,或通过环境变量注入 - 查 MySQL 服务端配置:执行
SELECT @@global.sql_mode;,确认不含PIPES_AS_CONCAT等可能辅助绕过的模式(虽然不直接相关,但常共存) - 测试验证:用已知注入点尝试提交
1'; SELECT 1; --,观察是否返回多结果集或报错Commands out of sync—— 后者说明多语句已被拦截 - 额外加固:即使禁用多语句,也要确保数据库账号无
FILE权限(REVOKE FILE ON *.* FROM 'app_user'@'%'),否则LOAD_FILE()仍可能被单语句利用
真正容易被忽略的是:有些 ORM(如旧版 MyBatis)在 <script></script> 标签里拼 SQL 时,底层仍走 JDBC 的 execute(),此时 allowMultiQueries 开关依然生效;而多数人只盯着自己写的 SQL,忘了框架封装层的执行路径。











