Python实现宽字节注入与布尔盲注自动化脚本
1. 从一条SQL注入链路说起为什么宽字节和布尔盲注要放在一起讲很多做Web安全入门的朋友第一次接触SQL注入时都是从联合查询注入Union-Based开始的因为回显直观、报错清晰一个order by就能把字段数摸出来。但真实环境里你遇到的情况往往是页面不回显数据、不报错、甚至连布尔状态都不明显。这时候就得靠布尔盲注一点点把数据问出来。而宽字节注入则是另一类场景——目标站点用了GBK之类的多字节编码开发者加了addslashes或者mysql_real_escape_string做转义你以为单引号被干掉了结果一个%df就把转义符吃掉了。把这两个技术点放在一起讲是因为它们经常在实战中组合出现目标站是GBK编码转义函数把单引号变成了\你用宽字节把反斜杠吃掉注入点重新打开但页面又不回显于是只能走布尔盲注的路子一个字符一个字符地猜。这条链路走通一次你对SQL注入的理解会上一个台阶。这篇文章我会用Python把整条链路实现一遍包括Payload构造、布尔条件判断、二分法加速、多线程优化这几个核心环节。所有代码都是本地搭建的模拟环境里跑通的不涉及任何真实目标。适合已经懂一点SQL注入基础、想往自动化方向走的朋友也适合想搞清楚盲注脚本到底怎么写的的开发者。看完你至少能自己写一个能跑的盲注工具而不是只会用现成的。提示本文所有演示均在本地自建的模拟靶场环境中进行目的是理解注入原理与防御思路。请勿将技术用于任何未授权的测试。2. 宽字节注入的原理拆解一个%df是怎么吃掉反斜杠的2.1 转义函数到底做了什么先搞清楚防御方的手段。PHP里最常见的转义是addslashes()它会在单引号、双引号、反斜杠\、NULL字符前面加一个反斜杠。比如你提交id1经过转义后变成id1\拼进SQL里就是SELECT * FROM users WHERE id 1\这样单引号就被关在字符串里了注入失败。mysql_real_escape_string()也是类似逻辑只不过它会考虑当前连接的字符集。关键点在于转义函数是按字节操作的它并不知道多字节编码的边界在哪里。这就是宽字节注入的突破口。2.2 GBK编码的贪吃特性GBK是双字节编码一个汉字占两个字节第一个字节的范围是0x81-0xFE第二个字节范围是0x40-0xFE去掉0x7F。当MySQL使用GBK字符集时它会按双字节去解析字符串。现在假设我们提交的payload是%df经过addslashes转义后前面被加了\0x5C于是字节序列变成%df 0x5C 0x27MySQL在GBK模式下读取时看到0xdf在0x81-0xFE范围内就认为它是一个双字节字符的首字节于是把后面的0x5C当作第二个字节一起吃掉组合成一个GBK汉字。剩下的0x27单引号就裸露出来了成功闭合字符串。这就是宽字节注入的核心用一个非法或合法的多字节首字节把转义符反斜杠吞进一个多字节字符里。2.3 为什么是%df而不是别的理论上0x81-0xFE范围内、且与0x5C组合后仍是合法GBK字符的字节都可以。%df之所以经典是因为0xdf5c在GBK里对应一个真实存在的汉字兼容性好不容易因为编码校验被拦。实际测试中%81、%82等也可以但有些会被WAF规则命中%df相对低调。这里有个容易踩的坑宽字节注入的前提是数据库连接字符集为GBK或GB2312等双字节编码。如果目标是UTF-8%df和0x5C不会组合成一个字符注入就失败了。所以第一步永远是判断字符集而不是上来就怼payload。2.4 判断字符集的实操方法我一般用两个手段交叉验证看HTTP响应头Content-Type: text/html; charsetgbk这是最直接的信号但页面编码不等于数据库连接编码只能作为参考。用%df测试提交id1%df如果页面报SQL语法错误比如You have an error in your SQL syntax说明单引号成功逃逸字符集大概率是GBK如果页面正常或者报非法字符那可能是UTF-8。还有一种更稳的方法提交id1%df and 11-- -和id1%df and 12-- -看两次响应是否有差异。有差异说明注入点成立可以继续往下走。3. 布尔盲注的判定逻辑没有回显时怎么问出数据3.1 布尔盲注的本质是二十问布尔盲注说白了就是玩二十问游戏。页面不会告诉你数据是什么但会对你的每个条件返回真或假两种状态。你通过构造and条件把数据是什么这个问题拆解成一系列是/否问题逐个字符、逐个比特地逼近答案。比如要猜数据库名第一个字符你可以问and ascii(substr(database(),1,1)) 100页面正常返回说明第一个字符的ASCII码大于100页面异常说明小于等于100。这样一次就能砍掉一半的可能性这就是二分法的威力。3.2 判定真和假的三种信号布尔盲注最难的不是构造payload而是准确识别页面状态。常见的判定信号有三类信号类型表现可靠性适用场景内容差异真时显示用户存在假时显示用户不存在高有明确文案反馈长度差异真时页面长度5000假时4800中内容动态但结构稳定状态码差异真时200假时500或302高有错误处理或跳转我个人的经验是优先用内容差异其次用长度差异状态码差异作为兜底。因为内容差异最稳定长度差异容易受动态内容比如时间戳、随机广告干扰状态码差异则可能被中间件统一处理掉。3.3 二分法为什么比线性快这么多假设要猜一个字符的ASCII码范围是0-127。线性遍历从0试到127平均要试64次最坏128次。二分法每次砍一半log2(128) 7次就能确定。一个数据库名假设8个字符线性要512次请求二分只要56次。如果再加上多线程速度差距是数量级的。这就是为什么所有正经的盲注脚本都用二分法。二分法的核心逻辑是维护一个low和high区间每次取中点mid问字符ASCII是否大于mid根据答案收缩区间直到low high。3.4 盲注中必须处理的边界问题实际写脚本时有几个边界必须处理字符集范围ASCII可打印字符是32-126但数据库名可能包含下划线、数字、字母实际范围可以缩到[48, 122]进一步减少请求次数。字符串长度得先猜长度用length(database()) n二分或者直接遍历1-20。结束条件当substr返回空字符串时说明字符串结束但二分法不好判断空通常先确定长度再逐字符猜。网络抖动偶尔一次请求超时不能当作假要加重试机制否则会猜错字符。4. 用Python把整条链路串起来从Payload构造到自动化脚本4.1 整体架构设计我写的这个脚本分四层请求层负责发送HTTP请求处理重试、超时、编码。判定层根据响应内容/长度判断布尔真假。注入层构造宽字节payload封装二分法猜字符、猜长度。调度层多线程并发猜多个位置的字符。这样分层的好处是换目标只需要改请求层和判定层注入逻辑不用动想加时间盲注只需要在判定层加一个基于延迟的判断。4.2 请求层用requests还是原生socket大部分情况用requests就够了它处理了连接池、编码、重定向。但有两个细节要注意关闭自动编码requests默认会根据响应头猜编码可能把GBK页面猜成ISO-8859-1导致中文乱码。我一般手动response.encoding gbk或者直接用response.content做字节比较。禁用重定向有些站点假条件会302跳转如果requests自动跟随你就分不清真假了。设allow_redirectsFalse。import requests def send_request(url, payload, timeout10, retries3): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept: text/html,application/xhtmlxml, } for i in range(retries): try: resp requests.get(url payload, headersheaders, timeouttimeout, allow_redirectsFalse) return resp except requests.RequestException: if i retries - 1: raise return None4.3 判定层怎么定义真判定层是整个脚本的眼睛。我一般先跑一次基准请求记录正常页面的长度和关键字符串然后定义def is_true(resp, baseline): if resp is None: return False # 优先看状态码 if resp.status_code ! 200: return False # 再看关键字符串 if baseline[keyword] in resp.text: return True # 最后看长度差异 if abs(len(resp.content) - baseline[length]) 50: return True return False这里的baseline是提前用and 11和and 12两次请求标定出来的。标定这一步不能省否则判定阈值全靠猜脚本会一直猜错。4.4 注入层宽字节payload的拼接宽字节payload的关键是把%df放在单引号前面并且注意URL编码。假设原始注入点是id1那么id1%df and ascii(substr(database(),1,1))64-- -在Python里拼接时%df要作为字面量保留不能让它被requests二次编码。我一般用params字典传参让requests自己处理编码但%df这种需要手动处理def build_payload(base, condition): # base 形如 ?id1 # 宽字节部分手动拼接 return base %df and condition -- -注意-- -后面的空格不能省MySQL的注释--要求后面跟空格或控制字符。4.5 二分法猜字符的完整实现def guess_char(url, base, position, baseline, low32, high126): while low high: mid (low high) // 2 condition fascii(substr(database(),{position},1)){mid} payload build_payload(base, condition) resp send_request(url, payload) if is_true(resp, baseline): low mid 1 else: high mid return chr(low)这段代码的逻辑是如果ascii mid为真说明目标字符在(mid, high]区间low mid 1否则在[low, mid]区间high mid。循环结束时low high就是答案。4.6 猜长度的实现长度猜法和字符类似只是把substr换成lengthdef guess_length(url, base, baseline, max_len30): low, high 1, max_len while low high: mid (low high) // 2 condition flength(database()){mid} payload build_payload(base, condition) resp send_request(url, payload) if is_true(resp, baseline): low mid 1 else: high mid return low4.7 多线程调度把串行变并行单线程猜一个8字符的库名按每字符7次请求算要56次请求每次1秒就是将近1分钟。如果库名长一点、还要猜表名字段名时间就爆炸了。多线程的思路是先猜出长度然后并发猜每个位置的字符因为位置之间互不依赖。from concurrent.futures import ThreadPoolExecutor def dump_string(url, base, baseline, length): result [] * length with ThreadPoolExecutor(max_workers8) as executor: futures { executor.submit(guess_char, url, base, i1, baseline): i for i in range(length) } for future in futures: idx futures[future] result[idx] future.result() return .join(result)max_workers不要设太大8-16比较合适。设太大容易把目标打挂也容易触发频率限制反而更慢。5. 实战调试记录那些文档里不会写的坑5.1 坑一%df被WAF拦了怎么办有些WAF会直接拦截%df这个特征。这时候可以换其他首字节比如%81、%82、%bf只要和0x5C组合后是合法GBK字符即可。我实测下来%bf比较稳因为0xbf5c对应的汉字不常见规则库覆盖少。还有一种情况是WAF拦substr、ascii这些函数名。可以用等价函数替换原函数替代方案substrmid、substringasciiord配合hexdatabase()schema_name从information_schema查空格/**/、%09、%0a5.2 坑二二分法猜出来的字符是错的这种情况90%是判定层不稳定。可能的原因页面有动态内容时间戳、随机token导致长度判定失效。解决办法是改用关键字符串判定或者多次请求取众数。网络抖动导致某次请求超时被当成假。解决办法是加重试超时后重发而不是直接返回False。目标有频率限制连续请求被限流返回错误页。解决办法是加延时或者降低并发。我的经验是每猜出一个字符用线性验证一次。比如二分猜出是a再发一次ascii(substr(...,1,1))97确认虽然多一次请求但能避免整串数据错位。5.3 坑三多线程下结果错位多线程猜字符时如果result列表的索引和线程提交顺序没对应好结果就会错位。上面代码里用futures字典把future映射到索引就是为了解决这个问题。千万不要用executor.map直接返回因为map保证顺序但会阻塞失去并发的意义也不要用列表append因为线程完成顺序不确定。5.4 坑四-- -注释在某些环境失效MySQL的--注释要求后面跟空格但有些环境比如某些ORM拼接会把空格吃掉。这时候可以用#代替但#在URL里要编码成%23。或者用/* */包裹兼容性最好。5.5 常见问题速查表现象可能原因排查方向所有条件都返回假判定层阈值不对重新标定baseline所有条件都返回真注入点没闭合检查%df是否生效猜出的字符乱码编码问题确认数据库字符集脚本跑一半卡住目标限流加延时、降并发长度猜不准动态内容干扰改用关键字符串判定5.6 一个提速小技巧预判字符集数据库名、表名、字段名通常只包含a-z、0-9、_ASCII范围是[48, 122]。把二分区间从[32, 126]缩到[48, 122]每次能少1-2次请求。别小看这点猜100个字符就是100-200次请求的差距。更进一步如果知道目标命名规范比如全小写字母加下划线可以先把区间缩到[95, 122]速度还能再快。这个技巧在猜表名、字段名时特别有用。6. 防御视角怎么让宽字节和盲注都失效写注入脚本的目的是理解原理最终还是要落到防御上。从防御方角度看宽字节注入和布尔盲注的根源都是用户输入被当成了SQL代码的一部分。6.1 根治方案参数化查询参数化查询Prepared Statement是唯一能从根本上解决SQL注入的方法。它的原理是把SQL语句的结构和参数分开传输数据库先编译好语句结构参数后填充参数永远被当作数据而不是代码。这样无论你提交%df还是 or 11-- -都只是普通字符串不会改变SQL语义。# 错误做法字符串拼接 cursor.execute(SELECT * FROM users WHERE id %s % user_id) # 正确做法参数化 cursor.execute(SELECT * FROM users WHERE id %s, (user_id,))6.2 字符集统一别用GBK如果业务允许数据库连接字符集统一用utf8mb4。UTF-8是多字节编码但它的首字节和后续字节有严格的位模式首字节110xxxxx、1110xxxx等0x5C不会和前面的字节组合成合法字符宽字节注入自然失效。如果历史原因必须用GBK那就要确保连接字符集、数据库字符集、表字符集三者一致并且用set names gbk显式设置避免MySQL用默认字符集解析。6.3 最小权限让注入即使成功也拿不到东西给Web应用的数据库账号只授予必要的权限只给SELECT、INSERT、UPDATE、DELETE不给DROP、FILE、EXECUTE。限制只能访问业务库不能访问information_schema虽然MySQL默认能访问但可以通过视图隔离。禁用LOAD_FILE、INTO OUTFILE等高危函数。这样即使注入成功攻击者能拿到的信息也有限。6.4 盲注的额外防御让真假难辨布尔盲注依赖真假状态的差异。防御方可以统一响应无论查询成功失败都返回相同的页面和状态码只在前端用JS区分。加随机延时每次请求随机延迟0-500ms干扰基于时间的判断。限制请求频率同一IP短时间内大量相似请求直接封禁。这些手段不能根治但能大幅提高盲注的成本让攻击者知难而退。7. 我个人的一些实操体会这套脚本我在本地模拟环境里反复调了大概两周最大的感受是盲注脚本的难点从来不是payload构造而是判定层的稳定性。payload网上到处都是但每个目标的响应特征都不一样判定层必须针对目标定制。我见过太多人拿着现成脚本直接怼结果因为判定不准猜出来的数据全是错的还以为是payload有问题。另一个体会是二分法的边界处理。low和high的初始值、循环条件low high还是low high、mid的取整方式这些细节稍微错一点结果就偏了。我建议先用一个已知答案的本地环境验证脚本比如猜一个固定的字符串确认二分逻辑没问题再上真实目标。最后说个心态问题盲注是个体力活一个字符7次请求猜一个库名可能就要几百次请求。别指望有什么一键拖库的神器那些工具底层也是这么一次次问出来的。理解了这个过程你再看那些自动化工具就知道它们每一步在干什么出了问题也知道从哪排查。这比会用工具本身重要得多。

相关新闻

致远OA V8.1数据字典:数据库字段的业务语义翻译器

致远OA V8.1数据字典:数据库字段的业务语义翻译器

简介:本资源是致远OA V8.1官方版本配套的结构化数据字典PDF文档,面向OA系统二次开发人员、数据库管理员及低代码集成工程师,用于快速掌握核心业务表结构与字段语义,支撑接口对接、报表开发、数据迁移及定制化扩展。文档完整呈现AD…

2026/10/17 14:02:56 阅读更多 →
提示词工程实战指南:从模糊需求到高质量AI输出

提示词工程实战指南:从模糊需求到高质量AI输出

前一阵子有朋友问我,AI聊天模型用起来总感觉“时好时坏”。同一件事,换个问法结果可能完全不一样,有时候输出专业得不像话,有时候又明显在胡编。我问他平时是怎么提问的,他说还能怎么问,把需求直接甩过去就…

2026/10/17 14:01:55 阅读更多 →
大模型微调最后一公里:从数据准备到部署上线的实战指南

大模型微调最后一公里:从数据准备到部署上线的实战指南

每次有人跑过来跟我说“我有个好想法,帮我微调一个大模型”,我都会先按住他,反问三个问题:你的数据长什么样?用户会在什么场景下打开你的产品?模型说错话的代价有多大?多数人愣一下之后就会发现…

2026/10/17 14:01:55 阅读更多 →

最新新闻

GNSS/INS松组合导航Matlab实现:卡尔曼滤波与误差状态详解

GNSS/INS松组合导航Matlab实现:卡尔曼滤波与误差状态详解

简介:面向航空航天、自动驾驶与航海等导航应用场景,这份GNSS/INS松组合导航Matlab程序适合需要研究卫星/惯性组合导航算法的工程师与在校生。程序围绕GNSS与INS数据融合展开:先由GNSS轨迹生成模拟惯导输出,再经卡尔曼滤波、四元数…

2026/10/17 16:56:54 阅读更多 →
Ubuntu 18.04下evo轨迹评估稳定实践指南

Ubuntu 18.04下evo轨迹评估稳定实践指南

1. 为什么在Ubuntu 18.04上坚持用evo——不是怀旧,是工程现场的刚性需求你可能在2024年听到“Ubuntu 18.04”会下意识皱眉:这系统早该退役了。但现实是,我去年参与的三个机器人SLAM项目、两个高校移动平台课程实验、以及某工业AGV定位模块的现…

2026/10/17 16:56:54 阅读更多 →
Artcraft:数字创作中艺术与工艺融合的方法论

Artcraft:数字创作中艺术与工艺融合的方法论

1. “Artcraft”不是新词,而是被长期误读的创作方法论原点“Artcraft”这个词最近突然在多个内容平台冒头,有人把它当AI绘画新模型,有人当成某款小众设计工具,还有人以为是某个海外艺术节的缩写。我第一次在某跨平台图像处理Demo的…

2026/10/17 16:56:54 阅读更多 →
Go接入微信支付API v3:签名、证书与回调验签全流程指南

Go接入微信支付API v3:签名、证书与回调验签全流程指南

1. 为什么偏偏是Go来写微信支付做开发这些年,微信支付接入应该算是“看起来简单、真正动手全是坑”的典型代表。尤其对Go开发者来说,网上搜到的资料要么是旧版API v2的XML体系,要么是半懂不懂的人拿Java代码硬转过来的伪Go写法,真…

2026/10/17 16:56:54 阅读更多 →
微信推送的“省着用“设计

微信推送的“省着用“设计

文章目录约束设计:三级分流队列:JSONL 文件锁Digest:每小时整理一次配额:三层防线为什么不用消息队列中间件总结约束 通过官方通道给微信发消息,有个硬约束:全天主动发送上限 10 条。超过就发不出去。 1…

2026/10/17 16:56:54 阅读更多 →
RAG切片策略指南:从原理到源码,解决知识库效果瓶颈

RAG切片策略指南:从原理到源码,解决知识库效果瓶颈

简介:这份源码资源面向正在搭建RAG应用、需要优化长文档切分环节的开发者与AI学习者,围绕检索增强生成中的切片策略展开,帮助解决文档切分粒度不当导致检索召回差、生成答案质量低的问题。资源包共5个文件,以Python脚本为主&#…

2026/10/17 16:55:53 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/16 14:23:36 阅读更多 →