
从第14关开始sql-lab才真正逼着你学会“盲注”这两个字。前面十几关玩得再开心报错注入也好、union注入也好页面至少会透露点信息给你。到了Less-14登录框就杵在那儿你往里面塞什么都只回你两种结果登录成功或者登录失败。数据库报错不存在的。字段数门都没有。union一把梭想都别想。这一关就是最典型的布尔盲注场景也是我当年刷sql-lab时第一次被卡住、后来彻底搞懂“盲注到底在干嘛”的分水岭。如果你已经刷完了前13关正卡在这个“怎么页面没反应”的关卡上或者你在真实渗透测试里遇到一个登录框死活不出数据这篇东西就是为你准备的。1. Less-14到底考的是什么1.1 关卡背景与源码逻辑还原Less-14是sqli-labs靶场里POST型注入的中段关卡表面上它和前几关一样是一个用户名密码的登录表单提交方式为POST。但和前面那些“友好”的关卡不同这一关把数据库的报错信息彻底关掉了不管SQL语句怎么出错页面上只会出现两个固定结果条件成立时显示登录成功条件不成立时显示登录失败。后端的查询逻辑大概是这样的基于sqli-labs常见源码版本还原$uname $_POST[uname]; $passwd $_POST[passwd]; if(isset($uname) isset($passwd)) { $sql SELECT username, password FROM users WHERE username\$uname\ AND password\$passwd\ LIMIT 0,1;; ... }注意这一行username\$uname\用户名的左右两边包的是双引号不是咱们前面十几关见惯了的单引号。MySQL默认支持用双引号包字符串值这一关就是故意在这个地方设了个坑。1.2 为什么这一关是分水岭前13关尤其是Less-5和Less-6其实已经出现过“没有数据回显”的情况但当时页面至少会明确告诉你SQL语法错误了没有。到了Less-14连这个“说话”的通道都被掐了你唯一能依赖的就是SQL查询本身真和假对页面结果的差异化反馈。这就是布尔盲注的核心模型把一个SQL查询改写成“真或假”的判断页面用登录成功和登录失败来替数据库回答你。你不是在“看”数据你是在“问”数据。每问一次只能得到0或1再把无数个0和1拼接成完整的信息。这种体验和前面的注入完全是两个维度。前面是“打开窗户看风景”这一关是“隔墙听声猜房间布局”。1.3 适合谁来刷这一关如果你是新手刚到这一关被卡住非常正常Less-14就是来打破“注入union出数据”这个惯性思维的。如果你已经会盲注这一关也是个很好的练手环境——它干净、没有额外过滤非常适合把布尔盲注的完整流程从头到尾走一遍。我建议刷到这一关时先别急着上sqlmap亲手用手工盲注把数据库名、表名、密码全猜出来一遍。后面你会发现用手工过程建立起的“条件思维”比记住任何工具命令都值钱。2. 注入点探测怎么确定它是双引号字符型2.1 先跑一遍基础探测组合进入Less-14页面后先别急着绕老老实实做闭合探测。我在这一关的常规操作是拿用户名位置开刀密码位置随便填一个固定值然后用下面几个组合去试admin单引号admin双引号admin --单引号加注释admin --双引号加注释admin and 11 --admin and 12 --我实测下来真实情况是这样的输入单引号页面没有任何变化输入双引号页面也没有任何变化。到这里很多人就懵了——“怎么都一个样”你别急关键在后面的组合测试。真正能确认问题的是admin and 12 --这组。前面填一个正确的用户名admin闭合双引号再用注释符把后面原本的密码条件干掉最后塞一个恒假的12。这时候页面从“登录成功”变成了“登录失败”说明条件式生效了双引号闭合成立。2.2 为什么单引号测不出来双引号能测出来这就要回到SQL的解析层面了。假设你输入的是admin后端拼出来的SQL是这样的SELECT username, password FROM users WHERE usernameadmin AND passwordxxx LIMIT 0,1;admin依然被双引号包裹着SQL语法没有失效只是查不到数据页面返回登录失败。所以单引号就像打在棉花上什么反馈都没有。而当你输入admin --拼出来的是SELECT username, password FROM users WHERE usernameadmin -- AND passwordxxx LIMIT 0,1;注意看你输入的把前面那个username的闭口提前闭合了--把后面所有SQL都注释掉了最终执行的语句变成SELECT username, password FROM users WHERE usernameadmin这就是为什么页面会显示登录成功——它真的在数据库里找到admin这条记录了。这个探测过程给我们的启发是字符型注入的第一步永远是“猜闭合符”单引号不行就换双引号双引号不行再想括号、反引号这些。很多人卡关就是因为在单引号上死磕。2.3 探测时的几个小坑第一密码框也可能存在注入但实战中一般先盯一个参数别两个参数同时加料否则出问题你都不知道是谁导致的。第二注释符后面建议加一个空格因为部分MySQL版本和中间件对--两个短横线加一个空格的解析有严格要求。第三一定先用and 12确认条件生效再拿and 11确认条件为真。如果11和12页面显示完全一样说明注入点判断逻辑有问题或者这个参数根本没拼进SQL。我见过不少人在这一步卡了很久反复试各种引号都没动静最后才发现他改的是密码参数而后端SQL只把用户名拼了进去——这种信息不对称在真实测试里更常见靶场至少还让你有据可查。3. 布尔盲注的核心原理3.1 页面只有两种状态怎么拿数据布尔盲注的核心是把数据“拆”成一个个条件让数据库回答“是或否”。打个比方你要猜一个保险箱密码你不可能直接问“密码是多少”你只能一次次地问“密码第一位是不是大于5”“是不是等于8”——数据库不会告诉你答案但页面会用“登录成功”或“登录失败”偷偷帮你回答。在Less-14里这个“回答”逻辑就是SQL语句查到了记录页面回头显示登录成功SQL语句没查到记录页面显示登录失败。于是我们把SQL改写成条件式例如让WHERE子句变成... WHERE usernameadmin AND length(database())10 -- ...如果数据库名长度确实大于10整个查询能查到admin记录页面显示登录成功如果不大于10查询结果为空页面显示登录失败。页面没有直接告诉我们数据库名长度是几但它告诉我们“长度是否大于10”。3.2 三个核心函数length、substr、ascii手工布尔盲注最常用的函数组合就三个length(expression)取表达式的长度用来确定数据总长度substr(expression, start, count)从表达式的结果里截取一段字符ascii(character)把字符转成对应的ASCII码数字为什么要用ascii()包一层因为SQL里直接比较字符会受到字符集和排序规则影响大小写、中文环境都可能出幺蛾子。转成ASCII码之后就成了纯数字比大小稳定又高效。比如我想问“当前数据库名的第1个字符是不是s”可以写成ascii(substr(database(),1,1)) 115s的ASCII码是115条件成立则页面登录成功不成立则登录失败。3.3 二分法让效率直接翻倍盲注没效率就是灾难。如果从a到z一个个猜平均每个字符要试13次但ASCII可打印字符范围是32到126一共95个如果用线性等值判断最坏要试95次。用二分法的话每次把区间砍一半95个字符最多只需要7次请求就能确定一个字符因为2的7次方等于128覆盖了95的范围。整个库名十几个字符手工也就一百来次请求写个脚本更是几秒钟的事。二分法的判断逻辑是先用ascii(substr(database(),1,1)) 78问是否大于N根据页面真假确定字符落在大半区还是小半区然后继续对半砍直到区间缩小到唯一值。这个思路在真实渗透里同样通用无论目标是布尔盲注、时间盲注还是带外注入二分法永远是提速的第一选择。我建议你在Less-14上至少完整手跑一遍二分法的流程不是为了省那几十次请求而是为了建立“用数字区间收敛答案”的直觉。后面你写自动化脚本、分析工具输出都会受益于这个直觉。4. 手工盲注完整实操从库名到数据4.1 先测库名长度进到实操环节。假设目标URL是http://127.0.0.1/sqli-labs/Less-14/POST参数是uname和passwd。我先做一次正常提交抓包确认数据包格式然后开始盲注。第一步永远是测长度。我习惯用大于号判断一层层往上加POST /sqli-labs/Less-14/ HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded unameadmin and length(database())5 -- passwdadmin如果页面显示成功说明库名长度大于5继续往上试。Less-14的库名是security长度为8所以当你试到length(database())8时会发现页面显示失败到length(database())8时显示成功于是确定了库名有8个字符。4.2 逐字符猜库名知道长度是8之后开始逐位猜字符。我先用二分法确定第1个字符。s的ASCII码是115我第一问会选一个中间值比如80unameadmin and ascii(substr(database(),1,1))80 -- passwdadmin条件为真说明第1个字符ASCII大于80。再问100仍然为真说明大于100。继续问110真115假。这样我就知道字符的ASCII落在111到115之间再问112、113、114最终收敛到115确定第1个字符是s。第2个字符同理把substr(database(),2,1)替换进去继续问。整个过程重复8次最后拼出security。熟练之后一个字符平均7次请求8个字符不到60次请求就能搞定手工操作大概十几分钟。4.3 从库名到表名到列名库名到手后下一步是表名。这需要把information_schema里的数据作为查询子集。比如查第一张表名的第一个字符payload长这样unameadmin and ascii(substr((select table_name from information_schema.tables where table_schemadatabase() limit 0,1),1,1))100 -- passwdadmin这里有三个括号嵌套很多人第一次写会漏括号导致条件恒真或恒假排查半天。我的经验是先在本地脑子里数清楚括号——substr套在ascii里select子查询套在substr里limit 0,1用来取第一行第一条。每次改查询对象时先把完整SQL在本地拼好跑一遍再压缩成payload不要直接在浏览器里边猜边改。表名确定后继续查列名。Less-14的users表里有username和password两列其中password字段的值是MD5加密过的直接盲注逐字符猜即可。4.4 手工盲注的耐心问题手工盲注最大的敌人不是技术是耐心。猜完库名8个字符你还要猜表名、列名、数据光users表里就十几条记录每条记录的密码又有32个MD5字符。纯手工全猜完运气好要几个小时手一抖填错一个值就得回头重试。所以我的建议是这样的Less-14上手工盲注的核心价值在于理解流程而不是真的把所有数据全手工掏出来。当你成功把库名security完整盲注出来的那一刻这关的“教学目的”就已经达到了。剩下的表、列、数据强烈建议写个Python脚本去跑既练了自动化能力又省时间。这里给一个参考的脚本思路用requests库发POST请求用string.printable或ASCII 32到126作为字符集用二分法收敛最后拼出完整结果。注意加time.sleep(0.1)控制频率避免把靶场服务打崩。5. 用SQLMap自动化收尾5.1 命令怎么写手工走通之后再用SQLMap验证一遍结果顺便对比一下工具的输出逻辑。Less-14是POST注入所以必须用--data参数指定POST体URL里不携带参数。python sqlmap.py -u http://127.0.0.1/sqli-labs/Less-14/ --dataunameadminpasswdadmin --dbs --batch--dbs是列出所有数据库--batch是自动选择默认选项省去交互过程。SQLMap会先发一堆探测请求很快就能识别出uname参数存在基于双引号的布尔盲注然后开始枚举。如果你看到输出里出现confirmed boolean-based blind之类的描述说明工具用的就是咱们手工走的那条路。之后想继续深入可以逐步换参数python sqlmap.py -u http://127.0.0.1/sqli-labs/Less-14/ --dataunameadminpasswdadmin -D security --tables --batch python sqlmap.py -u http://127.0.0.1/sqli-labs/Less-14/ --dataunameadminpasswdadmin -D security -T users --dump --batch第二句会把users表的所有数据脱下来密码字段默认是MD5SQLMap会尝试用内置字典破解。5.2 结果怎么理解SQLMap跑完后输出结果里有几个东西值得仔细看注入类型boolean-based blind、注入点参数parameter: uname、库名列表、表名和字段名。你拿SQLMap的最终结果和之前手工盲注猜出来的库名对一下如果一致说明手工过程没有出错。我实测过很多次SQLMap在Less-14上的默认检测基本一次就能识别出双引号闭合的布尔盲注不需要额外调--level和--risk。但如果哪天你遇到一个更复杂的闭合方式比如括号双引号混合SQLMap跑不出来时可以试试加--level3 --risk2它会尝试更多注入向量。5.3 工具不是终点工具能跑通不代表你可以不学原理。SQLMap跑这一关只用了十几秒但它不会告诉你为什么admin能闭合、不会告诉你为什么页面恰好能分成成功和失败两个状态、更不会告诉你在真实业务里有哪些情况会让SQLMap失效。我见过一些新人刷靶场直接sqlmap一把梭全部关卡都是秒出结果然后问他Less-14的注入类型、闭合方式、判断依据一个都答不上来。这种刷法基本等于白刷。正确节奏应该是先手工盲注成功一次跑通流程再让工具来验证和提速。工具是效率放大器不是思考替代品。6. 从Less-14看这类注入在真实世界的映射6.1 低危还是高危取决于数据Less-14模拟的是一个非常经典的登录框双引号注入场景在真实业务里这种注入往往出现在老旧的内部系统、遗留的PHP项目、或者一些外包开发的快速上线应用上。很多人觉得登录框只是验证账号密码的地方能有什么值钱的东西。但实际上登录框后面接的是用户表、订单表、甚至包含明文敏感信息的日志表。一旦注入点在登录框攻击者完全可以从“判断账号是否存在”开始逐步深入拿到全部数据甚至结合文件读写功能直接拿下服务器。所以对这类场景的严重程度评估底线不应该是“有没有注入”而应该是“注入能触达什么数据”。6.2 防御的本质是消除拼接Less-14的根本问题代码就一句话SQL用字符串拼接的方式把用户输入直接塞进了语句。防御手段一句话也能说清用参数化查询/预编译语句让用户输入永远只被当作数据解析而不是SQL代码。比如把查询改成$stmt $conn-prepare(SELECT username, password FROM users WHERE username ? AND password ?); $stmt-bind_param(ss, $uname, $passwd);这样无论攻击者输入什么驱动都会把它当作字符串值双引号、单引号、注释符全部失去“代码”意义。除此之外还应该做好数据库账号最小权限应用连接数据库的账号只给它查询所需表的权限不要给SELECT * FROM mysql.user这类权限。这样即使存在注入点攻击者能拿到的数据面也会被压缩到最小。同时关闭产品环境的详细错误回显别让SQL报错变成攻击者的信息收集工具。6.3 测试这类漏洞的边界意识说句实在话刷靶场和真实测试是两个完全不同的环境。靶场是合法学习的场所随便你折腾真实的业务系统必须建立在有明确授权的前提下。没有授权就去打别人的系统那叫破坏不叫安全测试。在真实授权测试中遇到这种登录框盲注我的习惯是先验证注入点是否真实存在闭合和恒真恒假判断再小范围确认能取到哪些库名然后立刻回到防护措施上做验证而不是一路盲注到底把所有数据都拖出来。点到为止既能证明危害又不越界。Less-14在整个sql-lab系列里扮演的角色是把“注入”从观赏性问题变成隐蔽性问题的转折点。过了这一关后面的Less-15、Less-16其实就是换不同的闭合方式、换不同的盲注形态反复锤炼同一个底层能力。我个人在实际带人时有个体会盲注的“盲”字面意思是页面信息的缺失但本质上它教会你的是——当直接路径走不通如何把大问题拆成无数个“是或否”再通过条件反射式的反馈重新累积出完整答案。这个思维模型不只在SQL注入里有用做代码审计、做逻辑漏洞挖掘、做数据比对分析到处都用得上。最后分享一个我在Less-14上踩过的小坑手测的时候因为浏览器和Burp对URL编码的处理方式不同payload里的空格和引号有时会被吃掉。建议在Burp的Repeater里直接提交原始数据包别在浏览器地址栏里折腾POST请求。另外如果你决定写脚本自动化记得在脚本里加一个请求间隔别一口气发几百个请求把自己靶场打挂了——真实测试里这叫对目标的基本尊重靶场里这叫对自己的环境负责。