首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
fastjson反序列化批量检测工具JsonExp的设计与实现
📅 2026/11/2 5:24:02
✍️ 爱科研究院
👁 阅读 3,247
1. 从一次内部演练说起为什么要自己动手做批量检测去年年底某公司做了一次内部安全演练红队只用了不到两个小时就从一台对外业务系统的日志里翻出了几条可疑的请求记录——全是精心构造的JSON报文目标直指一个老生常谈的组件fastjson。蓝队复盘的时候发现问题不在于没人知道fastjson有反序列化风险而在于没人能快速说清楚内网到底有多少个应用、多少个接口在用它、用的又是哪个版本。手工一个个接口去试几百个服务试到天亮也试不完。这就是我后来动手写JsonExp的直接动机。它不是什么高深的东西本质上就是一个针对fastjson反序列化风险的批量检测工具给定一批目标地址自动发送构造好的探测报文根据响应特征判断目标是否存在可利用的反序列化入口并把结果整理成一份能直接拿去汇报的清单。它解决的核心问题就三个字——规模化。单点验证谁都会难的是在几十上百个目标里快速筛出真正有问题的那些把人力从重复劳动里解放出来。这篇文章适合两类人看。一类是做安全测试、渗透评估的同行手里有一堆目标需要快速摸底另一类是做甲方安全建设的同学想搞清楚自己负责的资产面到底暴露了多少风险。哪怕你之前没写过检测工具只要懂一点HTTP请求和JSON格式跟着往下看也能把整套逻辑吃透。我会把设计思路、报文构造、批量调度、结果判定、踩过的坑全部摊开讲代码和参数都给到能直接复现的程度。需要提前说明的是本文讨论的所有技术细节仅用于授权范围内的安全测试与自查任何未经授权的探测行为都是不合规的这一点没有商量余地。2. 整体设计一个检测工具该长什么样2.1 核心需求拆解先想清楚要解决什么动手写代码之前我习惯先把需求拆成几条硬指标不然写着写着就跑偏了。JsonExp的需求我列了这么几条输入要灵活既能接受单个URL也能吃一个URL列表文件最好还能支持从其他扫描器的结果里导入。探测要准不能瞎发请求得能区分目标根本没解析JSON和解析了但没触发风险这两种情况否则误报能把人逼疯。速度要快几百个目标串行跑每个目标再发好几个探测报文时间成本受不了必须并发。结果要能看输出不能是一堆原始响应糊在屏幕上得结构化最好能导出成表格方便后续跟进。要能扩展fastjson的利用链和探测手法一直在演进工具得留出加新payload的口子不能写死。这几条需求里探测准确性是最难也最关键的。很多人写这类工具图省事就发一个经典payload看响应里有没有特定字符串有就报存在漏洞。这种做法在真实环境里误报率高得离谱因为目标可能压根没走到反序列化那一步或者被WAF拦了返回了个假页面。所以我在设计时把判定逻辑单独拎出来当成一个核心模块来做后面会详细讲。2.2 技术选型为什么用Python而不是别的工具用什么语言写我纠结过一阵。候选方案有三个Python、Go、Java。Go的并发模型确实香编译出来单文件分发也方便但写这类工具涉及大量HTTP细节处理和字符串操作Go的语法相对啰嗦快速迭代的成本高。Java本身就是fastjson的宿主语言写起来有天然亲和力但启动慢、依赖重做个轻量工具有点杀鸡用牛刀。最后选了Python理由很实在requests库处理HTTP太顺手concurrent.futures做并发池几行代码搞定字符串和JSON处理是Python的看家本领。而且这类检测工具的使用者大多是安全从业者Python环境基本人手一个拿到就能跑不用折腾编译。性能上Python确实不如Go但检测工具是IO密集型任务瓶颈在网络往返而不是CPU多线程足够把并发拉起来。提示如果你的目标规模特别大上千个或者对单机资源极其敏感可以考虑用asyncioaiohttp重写网络层吞吐能再上一个台阶。但对我遇到的场景线程池方案已经够用没必要过度设计。2.3 模块划分把大问题切成小块整个工具我拆成了五个模块各司其职模块职责关键点输入解析读取URL列表、去重、规范化处理协议头缺失、路径补全报文构造生成各类探测payload版本适配、编码处理请求调度并发发送、超时控制、重试线程池大小、异常兜底结果判定分析响应特征、给出结论多特征交叉验证、降误报结果输出结构化存储、导出表格化、可追溯这种切法的好处是每个模块都能单独测试、单独替换。比如后来我想加一种新的探测手法只需要在报文构造里加一个函数判定逻辑里加一条规则其他模块完全不用动。这种低耦合在工具演进过程中省了太多事。3. 核心细节探测报文到底怎么构造3.1 先搞懂原理fastjson的风险从哪来要构造有效的探测报文得先明白fastjson的反序列化机制。简单打个比方普通JSON反序列化就像照着快递单把东西放进对应的盒子盒子类型是固定的而fastjson支持一种特殊语法允许你在JSON里用type字段指定这个数据要还原成什么类。这个设计本意是为了方便但问题在于——如果攻击者能指定任意类而某些类在被实例化或调用setter方法时会执行危险操作比如加载远程资源、执行命令风险就产生了。所以探测的核心思路就是构造一个带type的JSON指向一个如果被解析就会产生可观测副作用的类。如果目标真的用fastjson解析了这个报文副作用就会发生我们通过响应或带外通道观察到它就能确认风险存在。这里有个关键认知不同版本的fastjson对type的限制不一样。早期版本几乎不设防后来陆续加了黑名单、白名单、safeMode等机制。所以探测报文不能一招鲜得针对不同版本准备不同的探针。3.2 探测payload的分类与选择我把payload分成三类各有各的用途第一类回显型探针。这类payload触发后会在HTTP响应里留下明显痕迹比如抛出一个包含特定字符串的异常。优点是判断直接不需要额外通道缺点是一旦目标关闭了错误回显就失效了。第二类延时型探针。触发后让服务端处理变慢通过响应时间差来判断。比如构造一个会触发大量计算的类。优点是隐蔽性好不依赖回显缺点是网络抖动会干扰判断需要多次采样取中位数。第三类带外型探针。触发后让目标主动向一个我们可控的地址发起请求通过是否收到请求来确认。这种方式最可靠但需要额外搭建接收端部署成本高一些。实际工具里我主要用前两类第三类作为补充。选择逻辑是先发回显型如果响应特征明显就直接下结论如果响应模棱两可比如返回了500但没看到预期字符串再补发延时型做二次确认。这种先快后稳的策略在准确率和效率之间取得了不错的平衡。3.3 报文构造的实操细节构造报文时有几个细节特别容易翻车我一个个说。编码问题。fastjson在解析前会做各种转义处理如果你的payload里含有特殊字符可能被目标的多层解析逻辑吃掉。我的做法是对关键字符做Unicode转义比如把type写成\u0040type这样能绕过一些简单的关键字过滤同时保证fastjson仍能正确识别。Content-Type。必须设成application/json否则很多框架压根不会把请求体当JSON处理。但有些老系统对Content-Type不敏感所以我在探测时会准备两个变体一个标准JSON头一个text/plain覆盖不同情况。路径选择。探测报文发给哪个接口理想情况是发给已知会解析JSON的业务接口。但批量检测时我们往往不知道具体接口所以我的策略是优先探测根路径和常见API路径如/api、/login、/user同时允许用户自定义路径列表。这里有个经验——登录接口和表单提交接口是重灾区因为这类接口天然要处理结构化数据。下面是一段报文构造的核心代码示意import json def build_probe_payload(probe_type, target_class): 构造探测报文 probe_type: echo | delay target_class: 探测指向的类名 base {type: target_class} if probe_type echo: # 回显型附加一个会触发异常标记的字段 base[marker] JsonExpProbe elif probe_type delay: # 延时型附加触发耗时操作的字段 base[count] 100000 # 对type做Unicode转义规避简单关键字过滤 raw json.dumps(base, ensure_asciiFalse) raw raw.replace(type, \\u0040type) return raw这段代码看着简单但ensure_asciiFalse和手动转义这两步是踩过坑才加上的。早期版本直接用json.dumps默认参数中文和特殊字符全被转成\uXXXX结果payload结构被破坏探测全失败。3.4 版本适配不同版本用不同探针fastjson的版本差异是绕不开的坎。我整理了一张对照表根据目标可能使用的版本区间选择探针版本区间主要限制机制推荐探针策略1.2.24及以下基本无限制经典回显探针即可1.2.25 - 1.2.47黑名单逐步加强需用绕过黑名单的类1.2.48 - 1.2.68黑名单autoType限制延时探针更可靠1.2.69及以上safeMode等强限制需确认是否开启safeMode实际检测时我们不知道目标版本所以策略是按版本从低到高依次尝试一旦某个探针命中就停止。这样既能覆盖老版本又不会在已修复的目标上浪费太多请求。当然请求数量要控制否则批量跑起来对目标压力太大也容易触发防护。4. 批量调度与结果判定工具好不好用全看这两块4.1 并发调度快而不乱的关键批量检测的性能瓶颈几乎全在网络IO上。假设单个目标探测需要发5个请求每个请求平均耗时300毫秒100个目标串行就是150秒加上超时重试可能翻倍。用线程池并发后理论上能压到几秒到十几秒。我用的是concurrent.futures.ThreadPoolExecutor核心参数是max_workers。这个值设多少有讲究设太小并发上不去设太大又会把目标打挂或者被限流。我的经验值是20到50之间具体看目标环境的承受能力。如果是内网自查可以开到50如果是对外评估建议保守点用20并且加上请求间隔。from concurrent.futures import ThreadPoolExecutor, as_completed def batch_scan(targets, max_workers30, timeout5): results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: future_map { executor.submit(scan_single, t, timeout): t for t in targets } for future in as_completed(future_map): target future_map[future] try: results.append(future.result()) except Exception as e: results.append({target: target, status: error, detail: str(e)}) return results这里有个细节as_completed配合future_map能保证即使某个任务抛异常也不会影响其他任务而且能记录下是哪个目标出的错。早期我图省事直接用executor.map结果一个目标超时抛异常整个批次都中断了排查半天才发现是这个原因。注意并发数不是越大越好。我实测过把max_workers从30提到100总耗时只降了不到20%但目标端出现连接拒绝的概率明显上升。原因是目标服务器的连接池和线程资源有限你发得再快它也处理不过来反而触发保护机制。4.2 超时与重试别让一个慢目标拖垮全局超时设置是批量工具的命门。设太短正常但稍慢的目标会被误判为失败设太长一个卡住的目标能把整个批次拖住。我的做法是分级超时连接超时设3秒读取超时设5秒整体请求超时设8秒。这样即使目标响应慢也不会无限等待。重试策略上我只对连接类错误重试比如连接被重置、超时对明确的HTTP错误响应不重试——因为后者说明目标已经给了答复重试没意义还增加负担。重试次数最多2次且每次重试前加一个随机延时0.5到1.5秒避免重试风暴。4.3 结果判定多特征交叉验证降误报这是整个工具最需要打磨的部分。前面说过简单看响应里有没有某个字符串误报率高得吓人。我总结了几条判定规则按可靠性从高到低排列规则一响应时间差。延时型探针触发后响应时间应该显著高于基线。我的判定标准是探测请求耗时 基线耗时 × 3且绝对差值 2秒。为了排除网络抖动基线取3次普通请求的中位数探测也发2次取中位数。这条规则可靠性最高因为时间差很难被伪造。规则二特定异常特征。回显型探针触发后响应体或响应头里会出现特定类的异常信息。我维护了一个特征库匹配到就加分。但要注意有些目标会把异常统一包装成通用错误页这时候特征就匹配不上了所以这条规则单独用不够得配合其他规则。规则三状态码异常。正常请求返回200探测请求返回500且响应体结构和正常错误页不同这是一个弱信号作为辅助判断。最终的判定逻辑是加权打分规则一命中记3分规则二命中记2分规则三命中记1分总分≥3判定为疑似存在≥5判定为确认存在低于3判定为未发现。这套打分机制比单一规则靠谱得多实测下来误报率能压到很低。判定结果分数区间后续动作建议确认存在≥5立即人工复核准备修复方案疑似存在3-4人工二次验证补充探测未发现3记录归档定期复测探测失败异常检查网络、目标可达性4.4 结果输出让人看得懂才算完成工具跑完结果得让人一眼看明白。我设计的输出包含这些字段目标地址、探测时间、判定结果、命中规则、响应耗时、备注。默认输出到控制台用表格展示同时支持导出CSV方便导入到其他管理系统里跟进。这里有个小技巧给每个结果加上证据链。比如判定为确认存在的把触发它的具体请求报文和响应片段也存下来。这样后续人工复核时不用重新跑一遍直接看证据就行。这个设计在汇报场景下特别有用领导问你凭什么说这个有问题直接把证据甩出来。5. 实操全流程从零跑通一次批量检测5.1 环境准备与依赖安装先把环境搭起来。Python版本建议3.8以上依赖就三个requests、concurrent.futures标准库自带、tabulate用于表格输出。pip install requests tabulate目录结构我习惯这样组织jsonexp/ ├── main.py # 入口 ├── scanner.py # 核心扫描逻辑 ├── payloads.py # 报文构造 ├── judge.py # 结果判定 └── targets.txt # 目标列表targets.txt每行一个目标支持带协议头或不带工具会自动补全http://192.168.1.10:8080 https://app.example.internal/api 10.0.0.5:90905.2 参数配置与选择依据工具启动时接受几个关键参数我逐个说明选择依据--workers并发线程数默认30。内网自查可调到50对外评估建议20。--timeout单请求超时默认8秒。目标响应普遍慢的话调到12秒。--retry重试次数默认2。网络不稳定时调到3。--paths探测路径列表默认/、/api、/login。可根据目标业务特点自定义。--output结果输出文件默认result.csv。这些默认值都是我在多次实战中调出来的不是拍脑袋定的。比如超时8秒是因为我统计过大量目标的响应时间分布95%的请求在3秒内返回设8秒能覆盖绝大多数情况又不至于等太久。5.3 一次完整的检测过程记录拿一个模拟的内网环境做演示目标列表里有20个地址其中3个是已知存在风险的测试靶机其余是正常服务。启动命令python main.py --targets targets.txt --workers 30 --timeout 8 --output result.csv运行过程中控制台会实时打印进度每个目标完成后输出一行状态。20个目标跑完用了大约40秒平均每个目标2秒。这个速度在可接受范围内。跑完看结果3个靶机全部被判定为确认存在其余17个正常服务判定为未发现没有误报。这个结果符合预期说明判定逻辑是可靠的。导出CSV后用表格软件打开字段清晰证据链完整。把这份结果交给负责修复的同事他们能直接定位到具体接口不用再来回问到底是哪个地址有问题。5.4 探测路径的自定义技巧默认路径覆盖不了所有场景。比如某个业务系统的JSON接口全在/gateway/下面那默认路径就探不到。这时候用--paths指定python main.py --targets targets.txt --paths /gateway/order,/gateway/user,/gateway/pay怎么知道该探哪些路径我的经验是先做一轮信息收集看看目标首页的JS文件里有没有API路径线索或者用浏览器开发者工具抓一下正常业务请求的路径。这一步花几分钟能让后续探测的命中率大幅提升。6. 踩坑实录那些文档里不会写的问题6.1 常见问题速查表现象可能原因排查与解决全部目标判定失败网络不通或代理配置错误先用curl手动测一个目标大量目标超时并发过高或目标限流降低workers加请求间隔误报率高判定规则太宽松提高打分阈值增加交叉验证探测报文被拦截目标有WAF或关键字过滤用Unicode转义变体换探测路径结果里出现重复目标输入列表未去重读取时用set去重延时探针不稳定网络抖动大增加采样次数取中位数6.2 几个印象深刻的坑坑一目标返回200但内容是错误页。早期版本我只判断状态码结果一堆目标返回200我以为是正常的后来一看响应体全是系统维护中。教训是状态码只能作为辅助信号必须结合响应体特征判断。现在我会检查响应体里有没有预期的业务关键字没有的话就标记为响应异常不参与打分。坑二Unicode转义把payload搞坏了。有次我为了绕过过滤把整个报文都做了Unicode转义结果fastjson解析失败探测全灭。后来才明白只能对type这个关键字做转义其他结构必须保持原样否则JSON结构就乱了。这个细节文档里不会写只能自己试出来。坑三并发太高把测试环境打挂了。有次在内网测试workers开到80结果把一台测试服务器打到无响应重启才恢复。从那以后我给自己定了规矩任何批量操作先小规模试跑5个目标确认没问题再全量。这个习惯救了我好几次。坑四结果文件被覆盖。默认输出文件名固定连续跑两次第一次的结果就被覆盖了。后来改成输出文件名带时间戳比如result_20240115_143022.csv再也没丢过数据。6.3 独家避坑心得分享几条我总结的、常规文档里看不到的经验探测前先做存活检测。对每个目标先发一个普通GET请求确认能通再发探测报文。这样能把目标不可达和目标无风险区分开避免把网络问题误判成安全结论。给探测请求加随机User-Agent。有些防护设备会拦截默认的Python-requests UA换个常见浏览器的UA能提高通过率。记录基线响应。每个目标在探测前先记录一次正常请求的响应时间、状态码、响应体长度作为后续判定的参照。没有基线延时判定就是空中楼阁。结果一定要人工复核。工具再准也只是辅助最终结论必须人工确认。我见过太多人直接拿工具结果去汇报结果被问细节时答不上来很尴尬。7. 工具的边界与后续演进方向7.1 这个工具能做什么、不能做什么得把话说清楚免得有人对它期望过高。JsonExp能做的快速批量筛查fastjson反序列化风险给出疑似目标清单和证据大幅压缩人工排查时间。它不能做的精确判定具体利用链、评估实际危害程度、替代专业渗透测试。工具给的是线索不是结论。另外要强调检测到风险不等于一定能利用。有些目标虽然存在反序列化入口但受限于类路径、依赖版本等因素实际可利用性很低。工具判定确认存在只是说明这里有个口子至于这个口子能撬开多大的门得靠人工深入分析。7.2 可以继续扩展的方向这个工具目前是个够用的版本但还有不少可以打磨的地方。比如把带外检测通道集成进来进一步提高判定准确性比如加一个指纹识别模块先判断目标是不是Java应用、有没有用fastjson再决定要不要发探测报文减少无效请求再比如把结果对接进常见的漏洞管理系统实现从检测到跟踪的闭环。我个人在实际操作中的体会是这类工具的价值不在于技术多复杂而在于能不能真正融进工作流。一个能一键跑、结果能直接用的简单工具远比一个功能强大但配置繁琐的工具受欢迎。所以后续演进我会优先考虑易用性而不是堆功能。最后分享一个小技巧如果你也要做类似的批量检测工具建议先把单个目标的检测逻辑跑通、跑准再考虑批量。很多人一上来就搞并发框架结果单点逻辑一堆bug批量跑起来全是噪音排查成本翻倍。单点稳了批量只是加个线程池的事顺序不能反。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/11/2 5:24:02
SAP系统集成:S/4HANA Launchpad与Work Zone内容同步实践
2026/11/2 5:24:02
从PRD自动生成Excel测试用例的高效方法
2026/11/2 5:24:02
Paperclip:OpenClaw本地AI工作流的隐形协议枢纽
2026/11/2 8:14:14
CAD带轮图库:机械设计效率与标准化的关键技术
2026/11/2 8:14:14
工业相机与2D激光雷达融合实战:球面投影+EKF在线标定
2026/11/2 8:14:14
静态链表:数组实现链表的精妙设计与应用
2026/11/2 8:14:14
RAT源码包安全风险与合规转向:检测取证、远程运维与样本分析
2026/11/2 8:14:14
SolidWorks二次开发模板:插件骨架、命令回调与避坑实践
2026/11/2 8:09:13
若依App版开发指南:UniApp+Vue移动端框架实践
2026/11/2 1:03:35
工业SPI隔离方案:K64主控+MAX14431从设计到排查
2026/11/2 1:03:35
SPI隔离器MAX14431与PIC18F8722实现工业级稳定通信的实战指南
2026/11/2 1:03:35
雷达信号调制技术解析:从LFM到相位连续调频波形设计
2026/11/2 2:42:45
面向复杂光照与角度的宠物面部识别鲁棒性增强策略
2026/11/2 2:42:47
DRV8818PWPR与R7FA8M1AHECBD工业步进驱动实战指南
2026/11/2 2:42:50
STM32与DRV8818双极步进电机驱动方案:从选型到调试全流程解析
2026/11/2 2:42:45
面向复杂光照与角度的宠物面部识别鲁棒性增强策略
2026/11/2 2:42:47
DRV8818PWPR与R7FA8M1AHECBD工业步进驱动实战指南
2026/11/2 2:42:50
STM32与DRV8818双极步进电机驱动方案:从选型到调试全流程解析