资讯动态

Sqli-lab SQL注入靶场实战:从闭合方式到盲注与绕过全解析

发布时间:2026/10/19 10:51:59 来源:尧图企业网站定制
1. 先搞清楚靶场的关卡设计逻辑接触过各类注入靶场的人应该都有这个感受很多靶场就是给你一个注入点让你猜payload猜不出来看WP看完了下一关还是不会。Sqli-lab不一样的地方在于它的关卡安排本身就是一条非常清晰的学习曲线从最简单的数字型注入一路走到HTTP头渗透中间还穿插了宽字节、二次注入、堆叠注入、order by注入这些实际业务里才会遇到的变体。我第一次完整刷这个靶场的时候最大的感受就是它逼着我把盲注这件事真正搞懂了。很多人在真实渗透里遇到盲注就只会丢给工具跑一旦工具跑不出来或者流量被拦截整个人就卡住了。但Sqli-lab前面几关恰恰是手工盲注的训练场从布尔盲注到时间盲注每一关都在强化同一个思路判断语句怎么执行、数据怎么逐位拆出来。这个靶场适合谁来刷我觉得有三类人第一类是想系统梳理SQL注入知识点的安全专业学生第二类是准备护网、渗透测试面试想把手艺重新拾起来的从业者第三类是写代码的研发人员——你至少要懂攻击者怎么测你的查询参数才能写出真正安全的SQL层代码。先说一个最核心的思路Sqli-lab的每一关本质上是同一个SQL语句的不同拼接形态。开发者写了个查询传参方式不同闭合方式不同过滤规则不同你就得用不同的姿势去闭合它、注释它、绕过它。所以刷靶场的重点不是背payload而是建立一套看到什么报错、响应什么特征就知道语句长什么样的推断能力。阶段大致关卡范围核心技能目标参数识别与闭合前10关左右判断注入点类型、闭合方式、注释符使用数据获取方式中段关卡union查询、报错注入、布尔/时间盲注注入位置迁移POST / HTTP头从GET参数转到POST正文和请求头过滤与绕过宽字节、关键字过滤等理解编码差异与过滤规则的可绕过性特殊场景注入堆叠、order by语句截断、堆叠执行、排序参数注入这个表格基本就是一个标准的自学路线图。下面我按这个路线逐层拆解每部分都会讲原理、讲手法、讲我实际踩过的坑。2. 从参数识别到闭合方式前期关卡的地基2.1 第一步永远是判断注入点很多人刷靶场喜欢直接上sqlmap这是很吃亏的。Sql-lab前期关卡设计得很良心报错全开你甚至能在页面上直接看到MySQL的语法报错。这时候一定要做一件事手工测。先看一个最典型的例子。一个基础的GET查询长这样http://example.com/less-1?id1页面正常显示数据。然后我加一个单引号http://example.com/less-1?id1如果页面报错说明这个参数很可能存在SQL注入。但报错不一定是好事不同数据库、不同语句拼接方式报错信息会完全不一样。比如MySQL的经典报错里经常出现SQL语法错误或者Unknown column之类的字样而在某些配置下数据库异常会被框架层吞掉直接返回500这时候就要靠页面内容差异来判断了。判断注入点还有一个经典做法数字运算。把参数改成id2-1如果页面显示的是id1对应的数据说明参数被代入SQL语句参与了运算大概率是数字型注入。如果页面依然显示id2的数据说明参数被当成字符串处理了这时候要做的就是字符串闭合。我自己的习惯是每次先用三种姿势打底单引号、2-1、1 and 11/1 and 12。三组请求一打基本能把参数是否可控、是否参与SQL拼接、是数字型还是字符型这三件事定下来。2.2 闭合方式字符型注入的核心数字型和字符型的区别很多人张嘴就能说但实际判断起来还是会翻车。数字型注入直接拼接数值语句长这样SELECT * FROM users WHERE id 1字符型注入则是在SQL里多了一层引号语句长这样SELECT * FROM users WHERE id 1如果你用id1去测数字型注入大概率只是报错因为多了一个单引号没人闭合。但如果是字符型你就可以通过插入单引号来破坏原有的SQL结构。比如SELECT * FROM users WHERE id 1这时候数据库会认为字符串1后面的是语法错误。怎么办用一个单引号把前面的引号闭合掉再注释掉后面的部分id1 --对应的SQL变成了SELECT * FROM users WHERE id 1 --MySQL里--是行注释符后面的内容全部被忽略语句就变成了WHERE id 1。这样注入字符串就成功落地了。Sqli-lab早期几关就是反复练这个事有的是1这种单引号闭合有的是1双引号闭合有的是(1)加括号的闭合。关键不是背而是要通过报错文本去反推原始语句的结构。页面报错里如果出现了类似1 LIMIT 0,1这样的字样你就能直接看到原语句长什么样用什么闭合一目了然。2.3 注释符那些坑注释符是注入里最容易翻车的一个细节。在MySQL里常用的注释符有三种#、--注意后面要跟一个空格、/* */。但HTTP请求里#会被浏览器当成锚点直接丢掉所以URL传参时要把#编码成%23。--这个写法为什么会流行因为在URL解码后会变成空格正好满足--后面需要空格的要求。还有一个容易踩的坑是GBK编码下的%00截断这个在PHP老版本里很常见。我用一个表把常用注释方式列出来写法实际效果使用场景----加空格GET传参时最常用%23#URL编码后传参##POST正文里直接用--%20--加空格部分场景下规范写法;%00分号加空字节老版本PHPMySQL的特殊场景我在刷Sqli-lab时发现早期关卡用--基本畅通无阻到了后面有些关卡把注释符过滤掉了就需要考虑用#或者闭合后面内容的其他方式。遇到这种情况要回去看源码层面的过滤逻辑而不是盲目换符号碰运气。3. 报错注入、布尔盲注与时间盲注三种盲区打法3.1 报错注入最快的数据提取方式当页面不回显数据但会把SQL错误信息打印出来的时候报错注入就是最高效的方式。Sqli-lab里从第五关开始就进入了这个模式页面不说人话不管你输入什么返回的都是那句经典的You are in但报错信息却会原原本本地暴露在页面上。MySQL里最常见的报错注入手法有三种extractvalue、updatexml、floor(rand(0)*2) group by。前两个本质上都是对XML文档函数传入非法参数MySQL会在错误信息里把函数参数原样返回。我用extractvalue举个例子AND extractvalue(1, concat(0x7e, (SELECT version()), 0x7e))这条语句的意思是用concat拼出一个包含查询结果的字符串传给extractvalue的第二参数。因为第一个参数不是合法的XML文档MySQL就会报错而且错误信息里会包含这个字符串。0x7e是波浪号~的十六进制表示用来包住查询结果防止出现不可见字符干扰阅读。实际操作里报错注入因为每个请求只能报出固定长度的信息所以依然要配合substr和limit来逐段提取。比如一次报错只能拿出32位左右那就AND extractvalue(1, concat(0x7e, substring((SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schemadatabase()), 1, 31)))报错注入最大的优点是可以直接出数据不像盲注那样一点一点猜最大的缺点是很多WAF都会对extractvalue、updatexml这些函数做精确匹配。刷Sqli-lab的时候遇到这类函数被过滤的关卡就要在报错函数的选择和绕过写法上动心思。3.2 布尔盲注把注入变成判断题布尔盲注的思路很简单构造一个条件分支让SQL语句的执行结果因条件真假而完全不同然后根据页面的不同表现来判断真伪。Sqli-lab布尔盲注关卡的页面特征是条件为真时正常显示一条记录条件为假时页面内容为空或者不显示。最常见的构造方式是用IF函数AND IF(ASCII(SUBSTR((SELECT DATABASE()), 1, 1)) 100, 1, 0)如果数据库名的第一个字符ASCII码大于100IF返回1整个条件成立页面正常否则返回0条件不成立页面异常。这样通过真假响应就可以把数据一位一位拆出来。逐位提取的时候最关键的是定位逻辑。我常用的定位语句是AND ORD(MID((SELECT IFNULL(CAST(DATABASE() AS CHAR), 0x20)), 1, 1)) 64很多人习惯用substr配合ascii我更喜欢用mid配合ord实际效果一样但代码写起来更顺。盲注最忌讳的是在一个请求里把多个判断条件叠在一起一旦响应特征模糊了你根本不知道是条件的问题还是语句的问题。每次只测一个字符通过二分法逐步逼近虽然慢但稳。在靶场里练布尔盲注的时候我强烈建议别开sqlmap纯手工跑。你会在一个字符上反复刷几十次请求这个过程很枯燥但正是这种枯燥让我真正理解了每次请求只能判断一个命题这个核心逻辑。跑完几个字符再去打开工具看它怎么工作很多东西就通了。3.3 时间盲注连响应内容都没有时的最后手段有些场景下页面不管条件真假返回的内容一模一样你什么都看不出来。这时候唯一可用的信息通道就是时间。Sqli-lab时间盲注关卡就是一个典型页面永远显示一句话真正的差别藏在响应时间上。时间盲注的核心是让条件真假导致SQL执行时间不同AND IF(ASCII(SUBSTR(DATABASE(), 1, 1)) 100, SLEEP(3), 0)条件为真时执行SLEEP(3)MySQL睡3秒HTTP响应时间就会明显增加条件为假时直接返回响应时间正常。这样一个请求一个请求地打根据响应时间差逐步判断数据。时间盲注在实际网络环境里有一个大问题网络抖动。你没法保证正常请求就一定是几十毫秒延迟和丢包都可能产生误判。我的经验是判断条件要用相对阈值而不是绝对时间比如把SLEEP(3)和正常响应时间做对比如果正常响应是100毫秒那超过2秒就视为真。另外要配合重试同一个条件至少打两次两次都超过阈值才记真否则判假。3.4 三种盲注的选型判断盲注类型依赖信息请求量速度适用场景报错注入页面显示SQL错误少快报错信息可见布尔盲注页面内容有差异多中条件开关特征明显时间盲注响应时间差异最多慢页面内容完全不变真实场景里我倾向于能报错就报错、能布尔就不时间时间盲注永远是最后的选择。因为时间盲注一个字符可能要打七八个请求一个数据库名几十个字符再算上表名、列名、数据内容整个提取过程的请求量会非常恐怖对目标系统和自身隐蔽性都是一种考验。4. HTTP头渗透从请求头里找入口4.1 为什么HTTP头会成为注入点到了Sqli-lab后段你会在某几关突然发现GET和POST参数都测不到注入点了页面完全正常这时候很多人就卡住了。其实入口藏在请求头里。HTTP头注入在实际开发里非常常见。一个典型的场景是开发者在后台做了一个登录日志功能把用户的User-Agent、Referer、X-Forwarded-For记录到数据库里方便排查问题。这个功能本身没问题但只要开发图省事把请求头内容直接拼进SQL语句注入口就出现了。你想象一下这个场景一个后台管理系统管理员查看登录日志列表日志表里存的是用户浏览器UA。攻击者只需要在请求里把UA改成恶意SQL管理员下次刷新日志页面的时候注入就触发了。这就是所谓的存储型注入——你的输入被存进了数据库在另一个页面被拼进SQL执行。Sqli-lab在HTTP头关卡设计上其实很接近这个真实场景。4.2 UA、Referer、XFF三大头的注入构造在Sqli-lab这类靶场里HTTP头注入集中在三个位置User-Agent的注入方式是在UA字段里直接构造闭合引号后的SQL语句User-Agent: 1 AND extractvalue(1, concat(0x7e, (SELECT version()), 0x7e)) --Referer注入的逻辑一样只是把注入串放在Referer字段里Referer: http://example.com/1 AND extractvalue(1, concat(0x7e, (SELECT version()), 0x7e)) --X-Forwarded-For的注入则比较特殊因为这个头一般用来记录客户端真实IP所以注入点是IP格式的拼接。有些开发者会把请求头里的XFF直接拼接进SQLX-Forwarded-For: 127.0.0.1 AND extractvalue(1, concat(0x7e, (SELECT version()), 0x7e)) --这三个位置的核心逻辑完全一样找到原始SQL语句对请求头的拼接位置用闭合符把引号闭合掉再用注释符把后面的内容吞掉。区别只是你需要在Burp里重新构造请求而不是在URL或POST参数里改。这也是为什么我建议刷到HTTP头关卡时一定要从浏览器切换到Burp Suite之类能自由修改请求头的工具否则你连注入点都摸不到。4.3 GET参数测不出来换个思路很多人测注入有个惯性思维先在URL参数里加单引号再在POST参数里加单引号都测不出来就认为没洞。但实际上HTTP头注入的触发点往往不在用户直接操作的参数里而是在服务端主动读取的元信息里。UA、Referer、Cookie、XFF、Host这些都可能是注入点。我个人的排查习惯是一个页面测完GET和POST参数之后一定把所有请求头都过一遍。尤其是当你看到目标系统有登录日志、操作审计、IP黑白名单这类功能时请求头注入的概率会直线上升。日志记录类功能喜欢存UA和RefererIP校验类功能喜欢读XFF和X-Real-IP这些字段只要进了SQL都是口子。4.4 头注入的几个常见坑头注入有几个比较隐秘的坑我一个个说。第一个坑是换行与注入语句的截断问题。有些语言和框架在解析HTTP头时会剥离换行符如果注入payload里带了回车换行可能导致请求头解析错误或者注入语句被截断。遇到这种情况要把payload里的换行符去掉或者改用其他字符替代。第二个坑是有些头字段存在格式校验。最典型的是XFF很多代码会先对XFF做IP格式校验你再怎么注入它也只会把非IP部分过滤掉。这时候可以尝试双写或者在合法IP后面拼接127.0.0.1 AND ...有些校验只检查开头几位不会做完整的IP解析。第三个坑是请求头的编码问题。在HTTP头里直接放中文或者特殊字符可能会被框架转义所以报错注入用的0x7e这种十六进制编码在头注入里特别实用可以避免很多由编码引起的干扰。5. 过滤与绕过在规则缝隙里找路5.1 关键字过滤大小写、双写与注释拆分过了基础关之后Sqli-lab会引入一些模拟真实开发场景的过滤逻辑。最常见的过滤就是针对关键字比如直接过滤掉select、union、and、or这些词。碰到这类关卡第一步是确认过滤规则到底是怎么写的。如果过滤方式是正则替换为空那就可以用双写绕过SELSELECTECT外层的是原词拆一半内层保留原词过滤函数把中间的内层词删掉后剩下的恰好是完整的SELECT数据库还是能识别。这个思路在关键字过滤的场景里非常经典。大小写混合也能绕过一部分过滤规则前提是过滤规则的匹配方式区分大小写。SeLeCt在MySQL里能正常执行但如果过滤逻辑只匹配小写的select就直接绕过了。不过现在很多过滤规则都做得很完善大小写混写不一定好用需要现场验证。注释拆分是另一个技巧。MySQL支持/*!50000SELECT*/这种内联注释注释内容会被当作SQL执行。Sqli-lab有些关卡的过滤规则不会对注释内容做检查于是你可以把关键字藏进注释里比如UNI/**/ON SEL/**/ECT过滤不了注释里的内容数据库却可以正常拼接执行。5.2 空格与符号替换空格在很多过滤规则里是重点监控对象。遇到空格被过滤的情况可以用注释符模拟空格UNION/**/SELECT/**/1,2,3/**/在SQL里等效于一个空格但很多过滤逻辑只盯着字面空格对注释符的管理往往更宽松。另外MySQL还有几个特殊的空白字符可以替代空格制表符%09、换行符%0a、回车符%0d。在URL参数里直接写这些编码有些过滤规则会漏掉。我刷Sqli-lab时印象很深的是有一关在URL里可以用%0a代替所有空格完全没被过滤。这种绕过方式在真实场景里同样有效所以遇到空格过滤不一定是死路先把能替代空格的字符都试一遍。5.3 宽字节注入的真原理宽字节注入是Sqli-lab里一个很有特色的关卡也是很多人理解得很模糊的一个知识点。它的产生条件有两个一是数据库编码为GBK这类双字节字符集二是前端或框架在拼接SQL前对单引号做了转义把改成了\。问题的关键在于在GBK编码下\的反斜杠编码是0x5c而某些汉字字符的编码是以0x5c结尾的。比如汉字運的GBK编码是0xbf5c当攻击者在输入里放了这个字再放一个单引号整个字符串在数据库眼里就变成了这样0xbf5c 5c27也就是運加上\。这时候0xbf5c被解析成一个汉字而0x5c27里的5c和27分别变成了另一个汉字的两个字节单引号就从转义状态中解脱出来了。这就是所谓的宽字节吃掉反斜杠。实际刷这关的时候没必要真的去翻GBK码表。你只需要知道在目标站点是GBK编码且对单引号做转义处理时可以试试在payload前面加上%bf%5c这类双字节字符前缀把转义用的反斜杠吸走。如果站点换成了UTF-8编码这个手法就基本失效了。这也是为什么宽字节注入只针对特定字符集环境不能当成万金油。5.4 堆叠注入与order by注入堆叠注入和order by注入是Sqli-lab后段的两类特殊关卡。堆叠注入的核心是分号。在SQL中分号标志着一条语句的结束你可以在注入点后加上分号再写一条完整的SQL语句让两条语句依次执行SELECT * FROM users WHERE id 1; DROP TABLE users堆叠注入能执行任意SQL语句所以危害远高于普通的union注入。但它有一个明显限制不是所有数据库和所有API都支持在一次查询里执行多条语句。MySQL的很多驱动默认是不支持堆叠查询的所以这关在实际环境里能不能通得看目标的技术栈。order by注入是我个人觉得最绕的一类。因为排序子句的语法限制你不能用union也不能随便用引号很多时候只能通过报错注入或者盲注去提取数据。排序注入的基础形式是在参数后面加判断条件ORDER BY IF(11, id, name)当条件为真时按一个字段排序条件为假时按另一个字段排序排序结果不同导致页面元素顺序变化这就是布尔盲注通道。遇到过滤严格的场景还可以在order by后面拼rand(0)配合group by触发报错。我在刷这个模块时最大的体会是order by注入不是一种独立的注入技术而是把前面所有注入知识综合用在一个语法受限的位置上。5.5 手工与工具什么时候该用什么很多人问我刷Sqli-lab到底该用手工还是该用sqlmap。我的判断标准很简单学习阶段必须手工实战阶段能上工具就上工具。Sqli-lab这种靶场设计的目的就是训练手工思路每一关都对应一种注入类型你在手工构造每一个payload的过程中才能真正理解参数拼接、闭合、注释、编码转换这些底层逻辑。直接跑sqlmap当然也能跑出来每个关卡都出数据但跑完了你还是只知道--dbs和-D那几个参数换个真实环境照样抓瞎。实战中恰恰相反时间就是生命手工盲注一个库可能得花几百上千个请求sqlmap几分钟就能搞定。所以我的建议是靶场里把手工练到条件反射实战里再依赖自动化工具做提速。两者不是对立关系而是先后关系。6. 卡关时的排查清单6.1 常见问题速查表刷到后段关卡卡关是常态。我把刷靶场过程中最常撞见的几个问题整理成一个速查表你照着这个表逐项排查大部分卡点都能解决卡关现象可能原因排查方向payload在GET里无效参数可能被URL编码变形用Burp直接改包观察原始请求报错信息不显示页面程序把错误吞了换布尔盲注或时间盲注单引号被转义低版本PHP走addslashes类函数尝试宽字节注入或换注入点空格被过滤存在基础WAF或过滤函数用注释符、%0a替代空格select被过滤关键字黑名单双写、注释拆分、大小写混用页面内容完全不变可能是时间盲注或HTTP头注入测响应时间差、排查请求头get请求测不出注入注入点可能在POST正文把参数从URL挪到请求体头字段被校验目标对头做了格式限制试试在合法值后面拼接payload6.2 学会从响应差异里读信息排查问题最核心的能力不是记住payload而是能从响应里读出有效信息。同一个注入点在报错信息可见和不可见两种状态下应对策略是完全不同的。所以每次请求都要关注三件事状态码有没有变、页面长度有没有变、响应时间有没有变。这三个特征就是你在盲区里判断条件的依据。我刷Sqli-lab时有一个习惯凡是页面内容发生变化的关卡我会先用curl把正常请求和异常请求的页面大小抓出来对比。如果正常请求是2350字节异常请求是1980字节那这个差的370字节就是你判断布尔条件的信号。把这个信号稳定下来盲注的准确率会大幅提升也不会因为偶尔一次的响应抖动而误判。6.3 一个老手才会注意到的小细节最后分享一个我在刷HTTP头渗透关卡时发现的细节。很多人在Burp里构造完请求头注入后会忘记处理Content-Length。如果你改了请求体或者加了请求头但Content-Length没跟着更新目标服务器会认为请求不完整直接断开连接你的注入就莫名其妙地失败了。不少人在这个坑里卡了很久。所以每次改完包先看一眼请求头里的长度字段再点发送能省很多排查时间。7. 刷完整个靶场之后我的实际感受Sqli-lab整体刷下来最大的收获不是记住了几十种payload而是建立了一套完整的注入排查思维。遇到一个目标先看参数怎么传再判断闭合方式接着尝试数据提取最后考虑过滤绕过的可能性。这个思维链路是我在真实渗透测试里反复用到的。我个人建议你刷靶场时准备一个笔记文档每一关都记录三样东西原始SQL语句长什么样、注入构造的关键一步是什么、如果过滤规则变了该用什么替代方案。这样刷完一遍你手里就相当于有了一本自己的注入手册比你收藏任何网上的wp都有价值。后面你还可以往两个方向延伸一个是把Sqli-lab里的注入手法迁移到其他类型的数据库上比如PostgreSQL和MSSQL的报错函数、注释符和堆叠支持都很不一样另一个是尝试绕过真实WAF产品把靶场里学到的绕过思路放到更严格的规则体系里验证。这两个方向都能让这套基本功转化成真正的实战能力。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑