Sqli-labs 这个靶场很老但直到今天我依然觉得它是适合系统练 SQL 注入思路的入门台阶。很多人刷到 Less23 之前很顺利到了 Less23~38 却卡住原因很简单前面的关卡几乎都贴着某一种注入类型直接给你看答案而从 Less23 开始题目开始有意给你制造“过滤”让你不能再用老套路。这一波不仅劝退新手也把很多人的思维固定在“用单引号闭合、注释符结尾”的舒适区里。这篇内容主要围绕 Sqli-labs 的 Less23 到 Less38 展开适合已经把前面基础关刷过一遍、想梳理绕过思路的人阅读。我会把每一段主线思路、典型 payload、踩坑点和排查方法都写清楚尽量让你刷这十几关的时候少走弯路。1. 为什么 Less23~38 才是 Sqli-labs 的精髓1.1 前22关在教“套路”这一章开始教“思路”Less1~22 基本可以按照“单引号闭合 → 判断列数 → 判断回显位 → 拿库表字段”这套流程一路推过去。这就是一个很标准的套路但它掩盖了一个关键问题你其实很少去思考为什么那个单引号能闭合、为什么注释符能截断 SQL。到了 Less23题目把-- 空格和#注释符同时过滤掉于是你发现原来无脑结尾的--突然失效了。你不重新思考 SQL 语句本身的结构就很容易卡在原地。所以 Less23 真正的目的不是考“怎么用 union”而是考“你能不能在没有注释符的情况下通过构造闭合条件让整条 SQL 在语法上成立”。这就是高级篇和基础篇最本质的区别基础篇教你怎么注入高级篇逼你理解为什么你能注入。1.2 高级篇的“敌人”不是数据库而是代码Less23~38 里几乎每一关都在模拟真实 Web 应用里的过滤代码比如全局转义、关键字替换、参数解析差异、编码问题。你真正要对抗的不再是 MySQL 的语法而是开发者写在 SQL 查询前的那几行防注入逻辑。搞清楚这一点你的学习方式就要变。不再是“找到一个能回显的 payload 就完事”而是先去判断过滤规则是什么再决定用哪一种绕过手段。比如过滤了#和--就要想怎么用单引号闭合。过滤了or、and就要考虑||、或双写。过滤了空格就要找替代字符比如%0a、%09、括号。对输入做了转义就要考虑宽字节或编码层面的问题。服务器有参数解析差异就要用 HPP 来打。这其实就是在模拟真实渗透测试里的“绕过”思维。你在靶场里多花时间想这些比多刷十个基础关更有价值。2. 高级注入的核心绕线思路2.1 先学会判断闭合方式无论目标怎么过滤第一步永远是判断 SQL 语句里参数是怎么拼接的。这听起来基础但高阶段容易忽略Less23~38 里几乎每一关的闭合方式都不完全一样常见的有单引号闭合$id被直接拼到$id里。双引号闭合$id这种情况在 Less31 附近出现。单引号加括号闭合($id)这种情况在 Less26a 等关卡出现。数字型直接拼接$id没有引号这种情况在 Less35 等宽字节关卡出现。POST 参数注入Less24、Less34 这类场景不是 GET 参数闭合方式一样要根据 SQL 语句判断。盲目套用1 and 11--是没用的。我的习惯是先分别提交1、1、1)、1))观察报错或页面差异再确定拼接近似结构。这一步多花两分钟后面能省半小时。2.2 注释符失效时的闭合技巧Less23 就是最典型的例子。你习惯性地在 payload 尾部加--结果发现被过滤。此时的重点是不需要注释掉 SQL 末尾的LIMIT 0,1或多余引号只需要让整条语句的引号成对出现。比如原 SQL 大概是SELECT * FROM users WHERE id$id LIMIT 0,1;如果不注释你提交id-1 union select 1,2,3拼出来的结果是SELECT * FROM users WHERE id-1 union select 1,2,3 LIMIT 0,1;后面的单引号和 SQL 自带的前置单引号形成闭合语法成立。这个思路就是“用引号闭合引号”而不是“用注释截断语句”。刷 Less23 时记住这个技巧后面的 Less26、Less28 也会用到。2.3 关键字过滤与空格过滤的应对到了 Less25过滤对象是or和and而且源码大概率是大小写不敏感地把这两个词替换为空。遇到这种正则替换第一反应是双写oorr替换掉中间的or后正好还原为or。Less26 更狠它把空格和注释符也过滤了。空格可以用%0a、%09、%0b等 MySQL 能接受的空白字符替代或者用括号把表达式括起来。比如原本union select中间的空格可以写成union%0aselect。关键字过滤的应对思路可以整理成几条能用运算符替代的就用运算符or用||and用。能双写还原的就双写oorr、anandd。能用大小写绕过的就大小写混合UnIoN SeLeCt。能用编码替代的就编码URL 编码、十六进制、注释符间隔等。没有一条是万能的你得知道过滤代码具体干了什么才能选对方法。2.4 宽字节注入的原理Less32~37 这组关卡的核心是宽字节注入也叫宽字符注入。它针对的是 PHP 里常见的转义函数比如addslashes这类函数会在单引号前面加一个反斜杠让单引号失去闭合作用。正常情况下$id addslashes($_GET[id]); // 1 变成 1\拼接进 SQL 就成了WHERE id1\ LIMIT 0,1;单引号被反斜杠转义注入失败。但在某些数据库连接字符集是 GBK 的情况下我们可以提交%df转义后变成%df\。在 GBK 编码里%df%5c即%df\会被解析成一个合法的宽字符反斜杠不再具有转义含义单引号就逃逸出来了。所以经典 payload 是?id1%df union select 1,database(),3--这个思路放在真实环境里需要满足两个前提数据库连接字符集是宽字节且支持0xdf5c这类组合输入值经过了转义处理。现在的系统大多默认 UTF-8宽字节注入没那么好用了但原理必须懂因为它是理解“编码导致安全边界失效”的最直观例子。2.5 二次注入与 HPP 的利用条件Less24 是二次注入它的特点是输入在写入数据库时没有被触发而是之后某个功能把这个值重新拼接到别的 SQL 语句里时才触发。所以你不能像普通注入那样提交一次参数就看到结果而是要走完整的业务流程比如注册一个恶意用户名再去修改密码。Less29~31 是 HTTP 参数污染HPP利用的是 Web 服务器和应用服务器对同名参数解析不一致的问题。WAF 可能检查的是第一个id后端 PHP 拿到的却是最后一个id于是构造?id1id-1 union select 1,database(),3--就绕过了 WAF。这个思路提醒我们过滤层和业务层对同一个请求的理解可能完全不同。3. 逐关实操记录Less23~38 通关复盘3.1 Less23~25注释、二次注入、and/or 过滤Less23 这关打开页面正常访问?id1能看到回显。尝试?id1报错说明有单引号闭合。再用?id1--发现还是报错这时就要意识到--和#被过滤了。换成闭合引号的方式?id-1 union select 1,database(),3注意结尾那个单引号是留给 SQL 语句原有引号闭合用的不是多余的。这里有一个细节URL 里的单引号不需要 URL 编码浏览器会直接传过去但如果你在某些工具里测试最好保持原样。Less24 是 POST 型二次注入界面是一个登录和注册功能。通关思路不是直接构造注入参数而是先注册一个恶意用户名再通过修改密码触发注入。常见做法是注册用户名admin--注意后面有个空格密码随意。注册成功后退出重新登录这个恶意用户然后在修改密码页面把新密码改成你想设置的任意值。提交后后台更新密码的 SQL 大致是UPDATE users SET passwordnewpass WHERE usernameadmin-- ;因为--把后面的内容注释掉条件就变成了WHERE usernameadmin于是管理员账号的密码被改掉。这个流程乍一看不像注入但它确实是典型的二次注入。刷这关时要养成一个习惯凡是数据入库后又会被取出来拼接的地方都要多留个心眼。Less25 过滤了or和and大小写敏感与否都无所谓直接用oorr双写绕过或者用||替代?id1 oorr 11-- ?id1 || 11--这关很简单但它为 Less26 这类后续关卡做了铺垫。我建议你在 Burp Suite 里把这几种 variant 都跑一遍观察哪些能返回正常页面加深印象。3.2 Less26~28a空格和关键字的组合过滤Less26 同时过滤了空格、注释符、or、and等需要在闭合单引号后把空格替换成%0a换行符把or替换成||或双写?id-1%0aunion%0aselect%0a1,database(),3%0a||%0a11这里要注意%0a在 URL 里会被后端解析成换行MySQL 在 SQL 语法上允许把换行当空白字符。实际测试时如果发现%0a不行可以换成%09Tab或%0b不同版本 MySQL 的容忍度略有差异。Less26a 的差别在闭合方式变成了单引号加括号。你可以先提交?id1)看报错再构造?id-1)%0aunion%0aselect%0a1,database(),3%0a||%0a(11核心思路一样只是闭合位置不同。Less27 和 Less28 主要考关键字过滤。Less27 把union select里的敏感词做了大小写不敏感的过滤但你可以用大小写混合绕过?id-1%0aUnIoN%0aSeLeCt%0a1,database(),3%0a||%0a11Less28 则进一步过滤了换行相关组合很多人习惯直接用%0aunion%0aselect这关可能直接被拦。此时可以尝试用%0b或者注释分隔符配合换行。总之这几个关卡的共同点是你得同时关注空格替代和关键字替代不能只解决其中一个问题。我给个建议遇到这类多过滤组合先用 Burp Suite 的 Repeater 逐个替换比如先只把空格换成%0a再看报错变化最后再处理关键字。别一股脑把 payload 写成最复杂的样子那样排查起来很痛苦。3.3 Less29~31HTTP 参数污染绕过 WAFLess29~31 是很有意思的一组它模拟的是 WAF 层和业务层对参数处理不一致的情况。页面上看也是 GET 参数注入但如果你只用传统单参数注入可能会发现某些 payload 被拦截。Less29 的做法是尝试提交两个同名的id参数?id1id-1 union select 1,database(),3--WAF 如果只校验第一个id1就会放行后端应用如果取的是最后一个id注入就生效了。当然具体取第一个还是最后一个取决于 Web 服务器和应用服务器的解析逻辑靶场已经帮你模拟好了你只需要按这个思路来。Less30 和 Less31 基本是同样思路下的变体区别在闭合方式或注入类型。Less30 可能是双引号闭合Less31 则可能加入了括号。遇到不同闭合方式时HPP 部分保持不变但要相应调整闭合字符。这组关卡给的真实经验不是“多参数”本身而是提醒我们过滤系统不一定能跟业务系统保持一致。在真实评估中如果目标有 WAF除了看 WAF 报错还要试试参数污染、参数拆分、编码混淆这些方式很可能问题就出在层与层的解析差异上。3.4 Less32~37宽字节转义逃逸Less32 是宽字节注入的入门关。你直接提交?id1会发现不出数据因为单引号被转义了。此时提交?id1%df union select 1,database(),3--就能看到回显。这关的坑在于%df必须紧贴着单引号中间不能有空格或其他字符。因为转义后的字符串是%df\在 GBK 字符集下%df%5c被当成一个中文字符反斜杠被吸收引号就出来了。如果你写成%df %27中间的空格会破坏编码组合注入直接失败。Less33、Less34 与 Less32 思路相同只是入参方式不同。Less34 是 POST 注入需要把%df放到 POST 参数里注意很多工具默认会对 POST 参数做 URL 编码你要确认最终发出去的字节是正确的。Less35 是数字型参数可能不需要闭合单引号但仍然有宽字节处理的逻辑。Less36、Less37 是前面的变体你可以当巩固练习来做。我在本地环境实际跑的时候花得最多的时间不是构造 payload而是排查编码。Sqli-labs 里能成功是因为它模拟了 GBK 连接配置。如果你自己搭环境一定记得把数据库连接字符集设置成 GBK否则%df后面那个反斜杠不会被吸收payload 就只能卡死。3.5 Less38堆叠注入的威力Less38 进入堆叠注入核心区别在于分号。在普通注入里我们通常只能执行一条查询语句因为 MySQL 驱动一般只允许一次查询。但在支持多语句执行的场景下你可以用一个分号结束第一条语句再接着写第二条甚至更多条语句?id1;insert into users(username,password) values(test,test)--这条 payload 执行后除了查询 id1 的用户还会向 users 表插入一条测试数据。你刷新页面看不到直接的“注入成功”提示需要去数据表里确认。堆叠注入的重点是它不再局限于 SELECT可以执行 UPDATE、INSERT、DELETE 等语句。但这并不能无脑用很多 Web 应用在框架层面就禁掉了多语句查询或者 MySQL 驱动本身有拦截。靶场里可能成功是因为它没有使用 PDO 预处理也没有限制CLIENT_MULTI_STATEMENTS。我建议你在 Less38 里把常用的读写操作都试一遍改密码、插用户、删数据。跑通之后再想想如果应用使用的是预编译语句这些注入还能不能成立。带着这个对比去学习你才对堆叠注入有真正的体感。4. 常见问题与排查技巧实录4.1 为什么 payload 单独测试对拼进 URL 就废了这是新手最容易碰到的问题。你在 SQL 编辑器里把拼接后的语句拿出来跑发现没问题但放进 URL 里就报错。十有八九是特殊字符没有正确传递。常见特殊字符包括#、空格、%、等。URL 里的#会被浏览器当作锚点根本不会发给服务器会被当作参数分隔符导致 payload 被截断。所以测试时尽量用 Repeater、命令行 curl 这类工具避免浏览器自动编码带来的干扰。另外--里的加号在某些场景下会被解码成空格但不同中间件处理逻辑不一样。如果发现--不生效可以试试--%20或者干脆用#前提是#没被过滤。我的排查顺序是先在 Repeater 里提交最原始的1看报错再用1--、1#分别试确认注释符是否有效最后再叠加上过滤绕过。不要一上来就丢一个长 payload。4.2 报错注入无回显时怎么调整Less23 到 Less38 里有几个关卡没有明显的联合查询回显或者因为过滤导致 union 结构不好构造。此时可以考虑报错注入方式比如updatexml和extractvalue?id1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)--但报错注入同样可能被过滤规则拦。比如关键字过滤、空格过滤都需要逐项替换。面对这种组合我一般会把 payload 拆成最小单元先测试and updatexml(1,1,1)能不能正常执行再往里填子查询。如果报错信息被屏蔽或页面完全空白就要从数据包返回差异入手用布尔条件判断比如1 and 11与1 and 12的页面差异。这个过程比较繁琐但它是后续盲注的基础。4.3 宽字节注入为什么中文环境常常失效宽字节注入不是万能的。它成立的核心条件是数据库连接字符集是宽字节比如 GBK而且输入的%df能和转义后产生的\组合成合法字符。很多人在本地环境复现失败是因为 MySQL 连接用了 UTF-8。这时%df%5c不会组成一个字符反斜杠仍然有效单引号照样被转义。要验证当前环境是否支持宽字节可以尝试在%df后面随便输入一个字符看是否出现乱码或报错信息变化。如果完全没有反应就先把连接字符集的问题解决了再继续否则你会在一个错误的前提上浪费大量时间。4.4 二次注入调试时最容易忽略的点Less24 很容易出现“明明注册了恶意用户名却修改不了管理员密码”的情况。原因可能是注册时输入框对用户名长度或字符格式做了限制或者注册功能本身对输入做了转义导致存储进去的内容和你的预期不一致。调试二次注入时我建议你先把最终入库的数据用 SQL 查询出来确认里面的单引号和注释符还在不在。很多情况下不是注入不成立而是注册阶段就把 payload 破坏了。另外要注意用户名里的空格--如果末尾没有空格在 MySQL 里注释不一定生效。这个细节要格外注意。5. 学习建议与必须守住的边界5.1 建议的练习顺序与记录方法如果你是从基础篇一路刷过来的可以先按 Less23 → Less24 → Less25 → Less26 → Less27 → Less28 → Less29 → Less32 → Less38 的顺序重点练。每一关不要急着看答案先自己尝试判断过滤规则再构造绕过 payload。我的记录习惯是建一个表格记下每一关的“过滤点”“闭合方式”“可用绕过方式”“最终 payload”。不要只记最终 payload因为那是答案不是思路。等刷到后面几关你会发现最有价值的反而是当时排除掉的失败尝试它们能帮你快速建立“什么情况不成立”的判断力。Less30、31、33、34、35、36、37 这些变体可以当作同思路的补充练习不必每一关都死磕。但如果你以后打算走 Web 安全方向我建议还是全部过一遍毕竟真实场景里遇到的过滤组合只会更复杂。5.2 必须牢记的安全边界这篇文章里的所有思路和 payload只应该用于本地靶场、自己搭的测试环境或者你已经获得明确书面授权的目标系统。SQL 注入是一种破坏性很强的安全漏洞随便拿线上系统练手轻则数据被改重则触犯法律。别把靶场经验直接搬到未经授权的站点上。我见过不少新手学完注入后第一反应是找个现成网站试试结果造成线上数据异常。安全测试的核心从来不是“能不能打进去”而是“在授权范围内验证风险并帮助修复问题”。做技术练习要有边界感这个意识比多学会几种绕过技巧重要得多。我自己在刷完 Less23~38 后最大的体会不是记住了多少种 payload而是学会了“先判断过滤逻辑再选择绕过策略”。如果你也能从这一章开始从背答案变成推逻辑那这十几关就没白刷。剩下的就是耐着性子去靶场里多试、多犯错、多记录。
阅读完成 · 觉得有帮助?