SQLi-Labs Less-3通关详解:单引号括号闭合与UNION注入全流程
发布时间:2026/9/26 11:40:46来源:尧图网络
很多人在SQLi-Labs靶场上有一个共同的卡点Less-1、Less-2顺风顺水到了Less-3就开始连着报错报得人怀疑人生。明明多出来的就是一个右括号但就是看不出来它藏在哪里。这篇文章我就把Less-3这个单引号括号字符型GET注入从头拆到尾从第一条报错如何反推后端SQL语句到判断列数、定位回显点再到用UNION查询一路拿到数据库里的用户名和密码。全程基于本机搭好的SQLi-Labs靶场环境适合刚学SQL注入、刷到Less-3被卡住的新手也适合想系统梳理闭合方式判断方法论的测试人员。1. Less-3 到底在考什么为什么说这一关是分水岭1.1 从Less-1到Less-4的递进关系SQLi-Labs这套靶场之所以经典是因为它把SQL注入最常见的情况按难度铺开每一关只专注一个知识点。Less-1是单引号字符型注入SQL语句大致长这样SELECT ... WHERE id$idLess-2是数字型注入SQL语句里id两边没有任何引号到了Less-3规则变了注入点是字符型但id外面除了单引号还套了一层括号。Less-4则更进一步从单引号变成双引号加括号。如果你只是照着网上的Payload抄一遍Less-3也能过但那样你错过的恰恰是这套靶场最有价值的部分从报错中还原后端SQL结构的能力。Less-1到Less-4的每一关都在反复训练同一件事——你怎么知道id在SQL语句里是被引号包着、被括号包着、还是被两者一起包着这种判断能力在真实测试里比任何Payload本身都重要因为现实世界的代码不会像靶场这样把漏洞摆在明面上。1.2 同样是字符型Less-3和Less-1的差别在哪Less-1的闭合方式很简单输入1就能看到报错然后在末尾加--注释掉后面的内容即可。Less-3的麻烦在于括号不是你在URL里输入的字符而是后端代码自己写死的。你输入一个单引号只会打断引号对括号还干干净净地待在那里这就导致了SQL语法错误。所以Less-3真正的考点有两个层面。表层是你知道要闭合括号深层是你能不能从报错信息里看出括号的存在。我见过很多人卡在这一关就是因为只盯着报错里的单引号看完全忽略了报错文本末尾那一小段near 1) LIMIT 0,1里的连续两个右括号。这一关过去之后你会自然形成一种条件反射看到报错先看near后面的内容那里藏着后端SQL的真实骨架。2. 报错信息是最诚实的老师从一条报错还原后端SQL2.1 手动输入单引号观察页面发生了什么在开始之前先把靶场环境准备好。我用的是PHPStudy搭的本地环境把SQLi-Labs解压到WWW目录下正常访问http://127.0.0.1/sqli-labs/Less-3/?id1页面会正常显示一行用户信息Your Login name: Dumb和Your Password: Dumb这是数据库中id1这条记录的内容。接下来是关键操作在地址栏把id值改成单引号http://127.0.0.1/sqli-labs/Less-3/?id1页面上方的报错区域立即出现红字。这里注意不要急着关掉页面仔细读报错文本You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1) LIMIT 0,1 at line 1如果你只看SQL syntax error几个字就划走那这一关你永远过不去。值得认真分析的其实是near后面那一小段。2.2 拆解报错文本找到缺失的右括号先看1)这一段。MySQL的报错会把你输入的内容连同被打断的SQL片段一起输出1是我们输入的部分可以理解为原来有一个左单引号加上我们输入的右单引号在MySQL内部合并成了两个连续的单引号。关键是最后那个)——它出现了两次说明原来SQL里id值外面本身就套着一个右括号。再看LIMIT 0,1这个信息也不能浪费。它说明原SQL语句末尾带了一个LIMIT子句每次只取一条记录。这解释了为什么我们注入后页面永远只显示一行数据。把这两段拼起来可以反推出后端SQL的逻辑结构大致是这样SELECT * FROM users WHERE id($id) LIMIT 0,1这个推断过程看起来简单但它是整套SQL注入手法的地基。Less-1的报错里你只会看到1 LIMIT 0,1没有多余的右括号Less-3多了一个)就多了一层需要闭合的结构。很多新手被卡住不是不知道SQL注入而是不懂得把报错当成后端代码的投影来读。顺便说一句热搜词里总有人搜括号计分左上角右下角这种括号听着可能有点歪但放到SQL注入里括号确实就是这么重要——少闭合一个右括号整条注入语句就是废的。2.3 用 id1) -- 验证闭合方式推断出闭合方式之后立刻验证。在URL中输入http://127.0.0.1/sqli-labs/Less-3/?id1) --如果页面恢复正常重新显示出Dumb的登录名和密码说明闭合方式判断正确。这里必须多说一句注释符的问题因为我在带新人时见过太多次栽在这里--中的在URL解码后会变成空格而MySQL的--注释符要求后面必须跟空格或控制字符才能生效。如果你写--但后面没有任何字符MySQL会把它当成普通文本后面的内容照样执行注入就会失败。更稳妥的替代方案是使用%23也就是URL编码后的#。MySQL中#也是注释符而且不需要后面跟空格http://127.0.0.1/sqli-labs/Less-3/?id1) %23两种方式二选一即可。我个人的习惯是优先用--因为它短写起来快但如果在某些场景下--无效第一反应就换成%23试试。3. 判断列数、定位回显点UNION注入的起手式3.1 为什么是 ORDER BY而不是别的办法闭合问题解决之后下一步是判断这个查询结果有几列。只有知道了列数后面的UNION SELECT才能跟上相同数量的字段否则UNION语法会直接报错。最常用的方法就是ORDER BYhttp://127.0.0.1/sqli-labs/Less-3/?id1) ORDER BY 1 -- http://127.0.0.1/sqli-labs/Less-3/?id1) ORDER BY 2 -- http://127.0.0.1/sqli-labs/Less-3/?id1) ORDER BY 3 --前三句执行下来页面都正常说明查询结果至少有三列。继续http://127.0.0.1/sqli-labs/Less-3/?id1) ORDER BY 4 --页面再次报错说明查询结果只有三列第四列根本不存在。有人会问为什么不直接试UNION SELECT 1,2,3,4看它报不报错这确实也可以。ORDER BY 的好处在于它不会受前面查询结果类型的影响判断结果更干净。而UNION SELECT如果列数对了但数据类型不匹配报错信息会把你的思路带偏新手容易误判。所以常规流程永远是ORDER BY先探列数再用UNION SELECT探回显点。3.2 LIMIT 0,1 带来的小陷阱这里有一个容易忽略的细节也是Less-3比Less-1稍微坑一点的地方原SQL末尾有LIMIT 0,1意味着原来的查询只返回一条记录。当我们注入UNION SELECT时UNION会把两个查询的结果合并但LIMIT依然作用在整个合并结果上。如果你写的是SELECT ... WHERE id(1) ORDER BY 3 LIMIT 0,1这时候UNION只有一条结果页面显示正常。但ORDER BY那一步本身是没问题的因为它作用的位置在UNION之前。真正要注意的是在第一步用id1) --验证闭合时页面显示的是原始数据而后面用UNION注入时如果想控制页面显示的哪一行可以通过修改id值来让前面的查询返回空集比如id0)或者id-1)这样UNION查询的结果就成了唯一的输出。这是一个非常实用的小技巧Less-3后面的Less-4到Less-6全都用得上。3.3 回显点确认2和3才是我们的阵地用UNION SELECT探回显点http://127.0.0.1/sqli-labs/Less-3/?id0) UNION SELECT 1,2,3 --这里的id0是为了让前面那个分支查不到数据从而让UNION后面的结果直接显示在页面上。执行之后页面上的Your Login name位置显示2Your Password位置显示3。也就是说第2列和第3列是回显点第1列不显示在页面上。后面的注入就固定用第2列或第3列来放置我们想查询的内容。比如要查数据库名就把database()放在第2列的位置。回显点的概念很多新手会绕晕为什么我写UNION SELECT 1,2,3页面显示的是2和3而数字1不见了因为后端PHP代码中只有第二个和第三个查询结果字段被输出到了页面第一个字段虽然存在于结果集中但PHP代码没把它打印出来。这跟SQL语句没关系纯粹是前端展示逻辑决定的。明白这一点后面你用哪一个位置放Payload心里就有数了。4. 从库名到底库数据一条URL走完整个流程4.1 获取数据库名回显点确定之后剩下的操作就是一条流水线。先拿当前数据库名http://127.0.0.1/sqli-labs/Less-3/?id0) UNION SELECT 1,database(),3 --页面上的Login name位置会显示security。这个security就是SQLi-Labs自带的数据库名Less-1到Less-4靶场的所有用户数据都存在这里。4.2 获取表名拿到库名之后查这个数据库下面的所有表。这里需要用到information_schema它是MySQL自带的元数据库记录了所有库、表、字段的信息http://127.0.0.1/sqli-labs/Less-3/?id0) UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schemasecurity --页面会输出一段用逗号分隔的表名列表包括emails、referers、uagents、users等。我们重点关注users因为用户名和密码都在里面。这里解释一下为什么用group_concatUNION查询要求前后两个SELECT返回的行数一致原查询只有一行结果如果你的第二个SELECT返回了多行UNION会自动合并但页面上只能显示一行。group_concat可以把多行结果拼成一行刚好绕过这个限制。如果不用它你就得靠LIMIT 1逐条查看效率低太多。4.3 获取字段名锁定users表之后查看这张表有哪些字段http://127.0.0.1/sqli-labs/Less-3/?id0) UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schemasecurity AND table_nameusers --返回结果包含id、username、password三个字段。SQLi-Labs这套靶场的表结构非常干净现实环境里可能有几十个字段但思路完全一样先确定目标表再确定目标字段。4.4 拖出所有用户名和密码字段名到手剩下的就是直接查询数据http://127.0.0.1/sqli-labs/Less-3/?id0) UNION SELECT 1,group_concat(username),group_concat(password) FROM users --页面上会一次性列出所有用户名和对应的密码明文。SQLi-Labs默认没有对密码做加密所以能直接看到Dumb、Angelina、Dummy、secure等一系列账号的密码。走到这一步Less-3的完整利用链路就通了判断注入点、还原SQL结构、闭合括号、ORDER BY探列、UNION测回显、查库名、查表名、查字段名、查数据。以后你刷Less-4、Less-5乃至其他靶场骨架都是这一套。5. 没有回显点的时候怎么办报错注入与布尔盲注的思路5.1 报错注入让数据库替我们说话UNION联合查询的前提是页面有回显点。如果目标页面的PHP代码把查询结果的每一列都写死在页面上那UNION注入就打了折扣。这时候报错注入就是一个非常可靠的备选方案。报错注入的核心思路是构造一个表达式让MySQL在执行时产生一个包含敏感信息的报错再把报错内容显示到页面上。Less-3的注入点是字符型所以报错注入的Payload写起来也不复杂http://127.0.0.1/sqli-labs/Less-3/?id1) AND updatexml(1,concat(0x7e,database()),1) --执行后页面的报错信息里会出现类似XPATH syntax error: ~security的内容~security中的security就是当前数据库名。原理是updatexml的第二个参数要求是XPath格式如果传入的不是合法XPathMySQL就会抛出错误并把参数内容回显在报错里。0x7e就是波浪号~用来做分隔符也防止某些情况下第一个字符被吞掉。这种手法在Less-5那种有数据但看不懂的关卡里是主力在Less-3里虽然杀鸡用牛刀但提前熟练一下后面会省很多力气。5.2 布尔盲注只有真假也要硬挖比报错注入更极端的情况是页面连报错都不显示成功时和失败时只有细微的页面差异。这种场景下就只能靠布尔盲注了。Less-3题目本身用不到盲注但思路可以在这里提前建立。判断一个条件是否成立比如数据库名的第一个字符是不是shttp://127.0.0.1/sqli-labs/Less-3/?id1) AND substr(database(),1,1)s --如果页面正常显示说明第一个字符确实是s如果页面空白或异常说明不是。用二分法逐字符爆破就能拼出完整的库名、表名和字段名。这个办法慢但在没有任何回显和报错的场景里它就是唯一的路。Less-8就是专门练这个的Less-3这里先把思路串起来就好。5.3 堆叠注入的简要说明Less-3也支持堆叠注入的尝试也就是在原有SQL语句后用分号结束再追加一条全新的SQL语句。比如http://127.0.0.1/sqli-labs/Less-3/?id1); UPDATE users SET passwordhacked WHERE usernameDumb; --如果后端支持同时执行多条语句且数据库连接配置允许堆叠注入能做的事情比UNION查询多得多——增删改都能做。但它的限制也很明显很多数据库驱动默认不支持多语句执行而且堆叠注入没有通用的回显方式更像执行了一个动作但看不到结果。在SQLi-Labs里Less-44之后的关卡才会系统演示这类问题Less-3只需要知道有这条路即可。6. 通关之后必须掌握的方法论如何快速判断任意闭合方式6.1 判断闭合方式的通用流程Less-3通关不难难的是把这一关的感觉沉淀成方法论。我梳理一下自己平时判断闭合方式的完整流程你可以直接拿去套用。第一步输入一个单引号观察报错。如果报错中出现相关的语法错误说明是字符型注入如果输入1后页面完全正常大概率是数字型或者单引号被转义了。第二步仔细读报错中near后面的文本。看里面出现了几个连续的右括号、有没有双引号、有没有其他特殊符号。这段文本就是后端SQL语句在报错点的快照。第三步根据快照尝试不同的闭合组合。常见的有这么几类后端写法示例闭合Payload说明$id1 --单引号字符型$id1 --数字型$id)1) --单引号加括号$id)1) --双引号加括号$id))1)) --单引号加双层括号第四步闭合成功后再依次做ORDER BY、UNION SELECT、查库表字段。整个过程如果哪一步报错回到第二步重新读报错而不是盲目更换Payload。6.2 我在其他关卡和场景中踩过的坑这套方法论看着简单实际执行起来有几个细节特别容易翻车。第一--的和空格问题。前面说过--后必须跟空格URL里用表示空格是比较通用的做法但有些老旧的中间件会私自改掉号导致注释符失效。遇见这种情况就果断换%23。第二单引号在URL中的传递。直接往地址栏敲id1时浏览器会原样发送单引号但如果你在某些Web表单或测试工具里输入工具可能会自作主张做编码导致你看到的请求和实际发送的不一样。排查这类问题时用Burp Suite抓包看原始请求最直接。第三information_schema在不同MySQL版本里的表现差异。老版本MySQL和MariaDB对某些字段的处理不完全一致但tables、columns这些最基础的视图在主流版本里都能用。如果你在别的靶场遇到查不到数据的情况先确认数据库版本再想其他办法。第四括号闭合不是只有一层。在真实代码里三层括号、四层括号都可能出现。我的习惯是先尝试一层括号不行就两层用ORDER BY的变化来验证——因为如果闭合方式错后面所有的注入语句都白搭。6.3 这类靶场练习对真实工作的实际意义最后说一点个人体会。很多人刷完SQLi-Labs会觉得这不都是练习题吗实际工作里谁会把SQL写成这样。但我在帮朋友做代码审计和测试时发现现实中的SQL拼接问题往往比靶场更隐蔽更难一眼看出来。靶场带给你的不是某个Payload本身而是两样东西一是从报错反推后端代码结构的逆向思维二是在多个闭合嵌套中快速定位边界的耐心。Less-3恰好是这两项能力的分水岭过了这一关后面的Less-4、Less-5本质上都是在同一个方法论上做变体。我当时刷Less-3的感觉是卡了两天有一天凌晨忽然盯着报错里的))看了半天然后用1) --一下就通了那种豁然开朗的感觉特别强烈。如果你现在也卡在这里不用急把报错多读几遍信息都在里面。
网站建设高端定制企业官网