尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
sqli-lab 38关之后:堆叠注入与ORDER BY注入实战拆解
从 Less-38 开始sqli-lab 给我的感觉一下子就变了。前 37 关基本是在一条单查询里折腾联合注入、报错、盲注、宽字节方法没变变的是过滤和闭合。到了 38 关注入点后面第一次出现了分号。这个分号让注入从“一句话里做文章”升级成了“一口气执行多条 SQL”。这篇接着记录 38 关到收尾的 Writeup重点拆解堆叠注入、ORDER BY 注入以及最后 54~65 关的挑战模式。如果你刚啃完前面 37 关准备往后刷这篇可以直接当路线图参考。1. 从38关开始第一个分水岭堆叠注入在堆什么1.1 普通注入和堆叠注入的本质区别普通注入无论怎么绕最终只能影响当前这条 SQL 语句。比如查询里写的是select * from users where id$id就算用union select把后面撑开后端执行的还是这一条 select。堆叠注入不一样它利用的是“分号结尾后还能再跟一条语句”的语义。你在参数里输入1;select sleep(3)--拼到 SQL 里就成了两条语句select * from users where id1;select sleep(3)--如果后端用的是支持多语句的接口比如 PHP 的mysqli_multi_query()MySQL 就会把分号前后的两条语句都执行掉。这跟前台看到的查询结果没关系select sleep(3)没有结果集但页面会明显卡三秒。从这一关开始题目考的不再是“怎么把查询结果拼到页面里”而是“后端到底允不允许你一次执行多件事”。1.2 为什么前面 37 关都不教堆叠并不是前面不存在这个漏洞而是 sqli-lab 的前 37 关使用的是普通查询接口源码里写的是mysqli_query()这种接口一次只能执行一条语句。你往参数里加分号MySQL 要么直接报错要么把分号后的内容忽略掉页面不会给任何反应。到了 38 关源码换成了mysqli_multi_query()教学意图很明确告诉你同一个 SQL 拼接漏洞在“单语句连接”和“多语句连接”两种模式下危害完全不同。我在实际测试里习惯把这种差距类比成电梯和楼梯普通查询像电梯你按一个楼层就只到那一层堆叠注入像是整栋楼梯你在某层打开门后还能继续往上走只要体力够走到哪一层取决于你输入多长。这个差异也是很多真实系统容易忽略的地方开发平时写 SQL 用的是普通接口上线后某个报表模块用了多语句接口一旦参数拼接点存在注入攻击面立刻变大。1.3 确认能不能堆叠只需要一个 sleep拿到一个注入点我最先做的是测闭合然后马上试;select sleep(3)。如果页面卡了 3 秒再出结果说明分号后面的第二条语句被真正执行了如果页面秒回或者直接报语法错误再考虑是不是闭合不对、是不是接口不支持多语句。这里有个容易踩的坑很多新手看到sleep()返回一个结果集会习惯性地去等页面回显那一行数字但堆叠注入的第二条查询结果一般不会显示最可靠的判断标准就是网络请求耗时。举个 Less-38 的例子。Less-38 是 GET 型单引号闭合我先请求?id1页面报错说明存在字符型注入点再请求GET /sqli-labs/Less-38/?id1;select sleep(3)-- HTTP/1.1响应时间比正常请求多了大约三秒堆叠确认。这套“测闭合 看延迟”的组合在整个 38~45 关里可以复用。2. 38~45 关堆叠注入的逐关拆解2.1 先整理一套闭合方式再打这组关卡一共有 8 关第 38~41 在 GET 参数里42~45 在 POST 参数里闭合方式基本就是前面 1~37 关重复过的那几种单引号、无引号、单引号加括号。我自己的习惯是先不急着堆叠而是用一条无害and 11和and 12确认闭合再开始堆叠。这里放一张我边刷边整理的对照表不同改版可能会有小差异但闭合和请求位置的规律是通用的关卡请求位置闭合方式探测示例Less-38GET id单引号1--Less-39GET id数字型1--Less-40GET id单引号加括号1)--Less-41GET id数字型1--Less-42POST passwd单引号1--Less-43POST passwd单引号加括号1)--Less-44POST passwd数字型1--Less-45POST passwd单引号加括号1)--如果你继续往后刷就会发现这个矩阵其实非常符合实战经验后端代码写习惯了同一个项目里很可能同时存在数字拼接和字符串拼接而且喜欢给查询条件套括号形成where (id$id)这样的结构。2.2 GET 型堆叠Less-38 到 Less-41Less-38 是整组题的样本。闭合方式确认后我在参数里拼一条 insertGET /sqli-labs/Less-38/?id1;insert into users(id,username,password) values(88,sqlinote,pass)--页面还是显示 id1 的正常数据但我用靶场自带的登录功能用sqlinote/pass登录成功说明这条 insert 真的写进了数据库。Less-39 和 Less-41 是数字型不需要单引号闭合直接从1;insert...--就能执行Less-40 是1)闭合payload 就变成1);insert...--。这三个变体值得记住的原因不是记忆 payload而是理解“怎么把 SQL 语义闭合完整”。单引号负责让你跳出字符串括号负责跳出子句后面的--负责把源码里本来会拼接的尾巴注释掉。跳得不够远后面语法就是错的注释不够干净后面内容会影响你执行的第二条语句。2.3 POST 型堆叠Less-42 到 Less-45POST 型堆叠和 GET 型在原理上完全一样区别在于参数位置从 URL 挪到了请求体而且通常出现在登录框。我习惯用抓包工具把passwd的值从1改成passwd1;insert into users(id,username,password) values(89,postnote,pass)--需要注意 POST 表单提交时的会被当作空格处理所以--到了后端其实是--正好满足 MySQL 对注释符后面必须留空的语法要求。很多新手在这里纠结“为什么写--而不是--%20”实际只要提交的是表单数据--就能正常工作如果数据是放 JSON 里传输反而优先用--或#。POST 型堆叠的应用比 GET 型更接近真实漏洞登录接口、更新资料接口、搜索框一旦使用多语句接口且参数可拼接攻击者不一定需要看到查询结果只要能执行一条 update 改掉管理员密码就足以造成危害。我在 Less-42 里就试过直接用 update 把所有用户密码改成同一个值这种“不关心回显只关心执行”的思路是堆叠注入最危险的地方。2.4 再进一步堆叠注入里的文件写入如果数据库账号有 FILE 权限并且 MySQL 的secure_file_priv允许写入指定目录堆叠注入还可以把第二条语句变成写文件操作。本地靶场里我为了验证整条链路会把 MySQL 的secure_file_priv临时设为空然后执行select ?php phpinfo();? into outfile /var/www/html/sqli-labs/shell.php写完后访问shell.php能看到 phpinfo 页面。这个例子我只建议在本地靶场里验证不要拿去测任何线上系统。它带来的思考是堆叠注入加高权限账号不只是拖库还有可能直接控制服务器这也是数据库账号权限最小化为什么那么重要。2.5 堆叠注入不能怎么玩堆叠不是万能的。第一如果后端使用 PDO 但关闭了多语句支持分号后的语句不会执行第二即使分号后的语句执行了如果第一条 select 没有结果集页面可能空白容易被误判为失败第三第二句如果是 insert 或 update对前端页面可能毫无影响需要靠其他功能验证。所以刷到后面我的验证习惯是先用 sleep 确认有没有执行再决定要不要来一条有影响的操作。3. 46~53 关ORDER BY 注入和堆叠的组合3.1 ORDER BY 为什么不能用 union从 Less-46 开始注入点变成了排序参数sortSQL 大概长这样select * from users order by $sortORDER BY 之后跟的不是查询出来的值而是一个“排序规则”所以union select那套在这里根本不成立。你可以试?sort1 union select 1,2,3但大多数情况下要么语法错误要么被当成排序列名。真正能用的只有三类报错注入、布尔盲注、堆叠注入而这三类在 46~53 关里全被过了一遍。3.2 报错注入在 ORDER BY 里怎么活数字型 ORDER BY 注入最省事的 payload 是?sort1 and extractvalue(1,concat(0x7e,database(),0x7e))因为order by后面可以跟表达式extractvalue报错内容会被页面带出来。如果排序参数有引号先闭合单引号再写and extractvalue(...)?sort1 and extractvalue(1,concat(0x7e,database(),0x7e))--我在刷 Less-47 时就是先用单引号探测出闭合再切换成报错注入。这类题的关键不是 payload 本身而是意识到“能排序的参数也是 SQL 片段的一部分”很多开发人员都默认排序参数是固定的列名结果直接拼进了查询。3.3 布尔盲注在 ORDER BY 里怎么判断如果报错内容被隐藏只能看到列表顺序那就用if表达式控制排序结果。比如判断数据库名的第一个字符是不是大于某个值?sortif(ascii(substr(database(),1,1))115,1,2)条件为真时数据按1排条件为假时按2排。通过观察第一行数据的用户名变化就能一位一位把数据库名抠出来。我习惯把这个过程和做选择题类比每条请求只回答一次“大于还是小于”然后拿二分法把答案逼近出来。Less-48、Less-49 就是这种盲注场景区别在有没有引号。3.4 在 ORDER BY 里继续堆叠Less-50 到 Less-53 又把堆叠带了回来只是在 ORDER BY 参数上。数字型可以直接?sort1;insert into users(id,username,password) values(90,orderby,pass)--有引号就先闭合?sort1;insert into users(id,username,password) values(91,orderby2,pass)--这里我体会到一件事靶场把“普通注入”和“堆叠注入”分开讲但真实系统中这两件事会叠加出现。一个排序参数如果既允许拼接又允许多语句那它就不再只是“拖库”或“写文件”而是可以直接往数据库里塞任何东西。4. 54~65 关挑战关里怎么用最少请求拿结果4.1 挑战关改动的地方到了 54 关题目会随机生成一张带 flag 的表并且每次请求次数被限制在个位数或十几位数。sqli-lab 这么设计练的是信息搜集效率你不再能靠一条条盲注慢慢猜必须在有限次数内把表名、列名、数据全拿出来。我刷挑战关之前先把前面 46~53 的报错注入方式熟悉了一遍因为报错注入可以在一次请求里带回一大段字符串比逐字符盲注效率高太多。当然挑战关也会换闭合方式所以第一步仍是拿1或1) 快速判断闭合。4.2 一次请求拿全表名以一次常见的挑战关为例闭合是单引号我直接用extractvalue报错分组函数结果?id1 and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schemadatabase()),0x7e))--页面的报错信息里会出现类似~users,emails,referers,uagents,secret_xxx~的一段内容目标表就在里面。extractvalue的报错回显通常有长度限制如果表名太多就补一个limit 0,1或者分批group_concat。4.3 再用一次请求拿列名和 flag拿到目标表名如secret_xxx后下一步用一个请求查列名?id1 and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_namesecret_xxx),0x7e))--返回内容里能看到id,something,flag之类的字段列表。接着把flag的值拿出来?id1 and extractvalue(1,concat(0x7e,(select flag from secret_xxx limit 0,1),0x7e))--一次请求一段一般三次到四次请求就能通关。如果字段值太长被截断可以用substr把值分成两段取。我遇到过有人在这里用group_concat把整张表数据都拼在报错里结果超过报错回显长度反而不如limit 0,1一条一条拿。4.4 报错被过滤就用二分法盲注如果挑战关把extractvalue、updatexml这类函数过滤了就只能回到布尔盲注。顺序仍然是先拿表名长度、再逐字符拿名字但挑战关次数有限手测几乎不可能得写脚本二分。我会在本地写个小脚本每次请求判断布尔条件用if(ascii(substr(...))x,1,0)控制排序或回显次数控制在字符集范围内的二分级别。这里最重要的不是脚本本身而是把“探测目标”拆成公式化的两步先定目标再定判断条件。4.5 挑战关对堆叠的态度挑战关一般不会要求你用堆叠因为多语句接口在真实环境里比较容易触发告警靶场更想考你单条语句内的信息提取能力。我自己刷的时候仍然会习惯性检查一次;select sleep(2)如果延迟生效说明这关也支持多语句那就可以用堆叠快速更新数据绕关如果不生效就老老实实报错或盲注。能用堆叠能节省好几条请求但不该把堆叠当默认选项。5. 常见问题与排查技巧5.1 闭合方式试探顺序我在整个 sqli-lab 后半段反复用到的试探顺序是1报错 →1报错 →1)报错 →1无报错。这个过程不是机械的而是看报错信息的样貌。如果单引号报错里带出了整段 SQL说明闭合就是单引号如果带出的是(...)说明外面套了括号如果怎么加引号都没变化大概率是数字型拼接。把这个顺序记牢后面所有关卡都能少走弯路。5.2 注释符被过滤或被解码的情况很多新手经常问“为什么--没用”。在 URL 里在某些服务端会被解析为空格所以--变成--这本来就是对的但如果 URL 经过编码层可能没转成空格MySQL 又把--后面跟上别的内容注释就不成立。更稳的做法是改用%23也就是#或者直接写--%20。POST 表单里用--比较顺因为表单解码会把加号还原为空格。注释符这块没有绝对万能答案建议在本地把三种都试一遍再记住当前环境下的偏好。5.3 堆叠注入没有延迟别急着怀疑 sleep我最初刷 38 关时遇到过sleep(3)不生效的情况后来发现是闭合没对语句语法本来就是错的自然谈不上延迟。如果闭合对了但还不生效再看接口是不是多语句。sqli-lab 在 38~53 的源码固定用多语句接口但如果你把同样的 payload 放到前面 1~37 关就不会延迟。只需要分清“语法错误”“不支持多语句”“语句执行但无回显”这三种状态排查会快很多。5.4 group_concat 和报错函数的长截断extractvalue和updatexml报错输出的长度有限大概只有几十个字符group_concat默认长度也可能只有 1024。数据一长就会被截断页面看到的一堆查询结果里只有一半。解决方法是给目标数据分段select substr(group_concat(...),1,64)、select substr(group_concat(...),65,64)。另一个经验是报错注入拿数据时尽量让字符串短一点用concat_ws控制分隔符而不是把所有字段都堆在一起。5.5 堆叠更新完数据页面没变化用 insert 或 update 验证堆叠时很多新手会说“明明执行了但页面没有任何变化”。这是因为第二条语句操作的是数据库不是当前查询结果。你可以去调用一个读取该数据的功能或者再看一次数据库表别指望当前页面会帮你打印提示。如果在靶场里只是验证最直观的还是;select sleep(3)延迟本身就是证据。6. 把后半段这些套路翻译成防御手段6.1 多语句接口是堆叠注入的土壤sqli-lab 从 38 关开始能堆叠不是因为 MySQL 本身有问题而是 PHP 选择了mysqli_multi_query()。真实项目里如果不需要一次执行多条语句就不应该开这个口子。开发用 PDO 的时候以为自带防护其实某些 PDO 配置也会允许多语句稍不留神就会踩坑。防御上最直接的写法是查询一律用预处理语句连接配置里显式关闭多语句排序参数走白名单不接受用户自由拼接。6.2 账号最小权限能挡掉一半写操作堆叠注入最疼的不是多查一条数据而是多执行一条 update 或者写一个文件。数据库账号只要不持有 FILE 权限into outfile这招基本失效只要没有 update 和 insert 权限堆叠造成的破坏面就小很多。靶场里你可能会觉得堆叠很好用但真实数据库一旦让业务账号拥有所有权限一次注入连备份都可能被删掉。最小权限原则不是上线规范里的一句口号而是渗透测试里最实在的止损手段。6.3 监控和告警比 WAF 更靠谱挑战关里的报错注入和布尔盲注特征其实很明显大量请求里反复出现information_schema、extractvalue、updatexml、ascii(substr(而且同一参数的值逐字符变化。与其依赖 WAF 的规则库不如在数据库审计日志里对这类关键词做统计请求频率异常时直接告警。我自己刷完这二十几关最大的感受是漏洞利用的变形能力很强但“在数据库层面对异常 SQL 进行审计”这条基线从来没变过。这套后半段刷下来的经验我自己最想留下的不是某一条 payload而是那个排查顺序先判断注入点是什么拼接形态再判断能不能堆叠最后才决定用报错还是盲注。sqli-lab 把同一个知识点翻来覆去换了二十遍包装就是在训练这种肌肉记忆。如果你也在刷某一个系列别急着背答案建议每关在笔记里画一张参数、闭合、接口模式的表格刷到后面会发现很多东西都是排列组合。
RELATED

相关推荐

结构监测中频响函数漂移与时变结构分析:从异常数据到实用方法

结构监测中频响函数漂移与时变结构分析:从异常数据到实用方法

做结构监测这些年,我遇到过不少一开始让人怀疑人生的数据。有一回在厂区设备基座上做振动测试,同样的测点、同样的设备、同样的激励方式,凌晨记录到的第一阶固有频率是6.85Hz,到了下午,同一测点居然变成了7.02Hz。一开…

📅 2026/10/17 17:10:08
凸优化从理论到代码:梯度下降、牛顿法、内点法与ADMM实战

凸优化从理论到代码:梯度下降、牛顿法、内点法与ADMM实战

简介:这份资源是凸优化理论学习的心得配套代码包,面向正在入门或希望系统梳理凸优化知识体系的机器学习、数据分析方向学习者。内容围绕学习路径展开,涵盖入门书籍选择、Boyd《凸优化》结构解读、线性代数与微积分基础铺垫,以及多…

📅 2026/10/17 17:10:08
鲲鹏云大数据实验:ARM节点Hadoop 2.8.3部署与OBS联通实操

鲲鹏云大数据实验:ARM节点Hadoop 2.8.3部署与OBS联通实操

简介:面向鲲鹏云大数据实验的Word版报告,适合在华为云环境学习Hadoop集群搭建的开发者与学生使用。资源为1份docx文档,大小2.52MB,聚焦从购买ECS与OBS、获取AK/SK到完成NameNode初始化与HDFS启动的全过程。全文按节点维度记录Node…

📅 2026/10/17 17:10:08
MORE NEWS

更多资讯

📰

LongCat-Flash-Lite-Sparse-8bit 4-bit/6-bit/8-bit怎么选?附Mac内存需求对照与量化选型指南

【免费下载链接】LongCat-Flash-Lite-Sparse-8bit 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/LongCat-Flash-Lite-Sparse-8bit 点击查看 免费下载 LongCat-Flash-Lite-Sparse-8bit 是美团 69B-A3B MoE 模型 LongCat-Flash-Lite-Sparse 的 MLX 8-…

📰

Spring Boot整合Redis:缓存、序列化与分布式锁实战

做后端开发,Redis基本是绕不开的组件。我第一次被它圈粉,是因为有个内部系统每天高峰期被同一个查询接口打成筛子,数据库CPU飙到99%,加了Redis缓存之后,QPS翻了几倍还不止,数据库负载直接掉到20%。后来我在…

📰

企业级智能体难在工程:OpenClaw部署与治理全解析

1. 先破除一个误区:企业级智能体之难,难在工程而非模型很多团队拿到 OpenClaw 的第一反应,是急着调模型、叠 Prompt、压测对话效果。我见过不少项目,Demo 演示时全屋惊叹,结果一到内网部署、连上真实业务系统&#xff…

📰

Windows平台XDMA驱动开发与PCIe高速传输工程实践

1. 项目概述1.1 为什么突然聊起 XDMA做 FPGA 数据采集和高性能传输的同仁,最近一定绕不开 XDMA 这个词。尤其是当你手里的 PCIe 项目从 Linux 环境迁到 Windows 平台,或者需要在 Windows 上做视频采集、高速 ADC 数据回放、NVMe 协议桥接这类应用时&…

📰

Kimi K2 + Claude Code 接入 3 个神级开源 MCP,相当丝滑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Keras-bert模型部署实战:用Flask封装文本分类预测服务

模型预测跑通只是第一步,把模型交到别人手里才算真上线。这篇Keras入门第七篇,我用Flask把Keras-bert训练出来的文本分类模型包成一个HTTP预测服务,让业务方直接通过接口调用,而不是把模型文件丢过去让人家自己跑脚本。适合已经训…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬