ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

marshalsea 反序列化 Gadget 实验室挑战指南:从新增 Sink 标签到突破严格白名单检测器

marshalsea 反序列化 Gadget 实验室挑战指南:从新增 Sink 标签到突破严格白名单检测器 【免费下载链接】Cybersecurity-ProjectsBuilding 70 Projects ranging from beginner to advanced so anyone can — learn from, build upon, use as a reference, or even copy directly. Gamified Cybersecurity learning 项目地址https://gitcode.com/gh_mirrors/cy/Cybersecurity-Projects点击查看免费下载本文是deserialization-gadget-lab仓库中 04-CHALLENGES.md 的完整展开。它把仓库中一座已经完工的反序列化安全实验台268 个测试、六道闸门、79 条断言全绿变成了 14 道难度递增的动手练习从给 Marshal 解析器新增一个 Sink 标签到写一条真实 CVE 的 gadget 链再到设法把载荷送过strict_allowlist检测器。读完本文并亲手完成这些挑战你将同时具备攻击与防御两侧的实操能力读懂 Marshal 二进制格式、写出不自我引爆的 payload、用 TracePoint 实现真正的加载前否决以及用可复现的字节串证明一个检测规则是否真的有效。开始之前用命令行建立验收基准原文档给出了一组入口命令它们是所有挑战的公共前提全部通过 justfile 执行并在固定版本的ruby:4.0-slim容器内运行--network none、只读挂载just check # 七个测试套件 独立控制脚本 just gate # 全部六道闸门约十分钟运行真实容器 just corpus # 每一个对抗性载荷以及检测器对它的裁决 just scan # gadget 扫描器扫描当前进程已加载的类图 just lint # rubocop覆盖 37 个文件对照 justfile 的gate定义可以看到完整链路是check matrix exploit detector target package六个阶段corpus直接驱动test/support/adversarial_corpus.rb里的每一个用例并把 accept/reject 与 reason 打印成一张表scan则是Marshalsea::Scanner.new(...).scan的一次裸调用。每个挑战的验收标准绿套件 一个会变红的测试这是整个章节最重要的一段话原文原文如此下面的每个挑战都应当以两种方式结束一套全绿的测试和一个在你改动之前会失败的测试。后一半才是重点。理由写在数字里这个项目曾在 110 个通过的测试之下带着两个检测器绕过 shipped发布。因此“套件全绿”本身什么也证明不了。任何一条新规则的验收方式都是同一句纪律——如果你加了一条声称“代码做了 X”的规则那么先动手把 X 弄坏看着一个测试变红再去相信它。这一纪律与 03-IMPLEMENTATION.md 中“每条规则都要配一个能杀掉它的 mutant”的实践是一脉相承的。热身Warm-ups四个小改动建立肌肉记忆四个热身题都刻意设计成“你几乎什么都不用碰”的规模目的是让你亲手发现这座实验室的结构究竟把逻辑放到了哪里。1. 给解析器新增一个它不知道的 Sink 标签目前解析器认识的三个 Sink 标签是u、U、d分别映射到_load、marshal_load、_load_data。这张表位于 lib/marshalsea/marshal/constants.rb 的Constants::SINK_METHODSSINK_METHODS { TAG_USERDEF _load, # u - Klass._load TAG_USERMARSHAL marshal_load, # U - obj.marshal_load TAG_DATA _load_data # d - Klass._load_data }.freeze练习从 Marshal 格式里再挑一个标签决定它应该派发什么方法并把它加进这张表。要证明它生效你需要两条互逆的语料证据——一条必须被拒绝该标签出现在流中另一条必须在类名仅仅出现在 value 位置时不触发。这道题的点睛之处在于发现改动面有多小SINK_METHODS是一张冻结常量Node#sink?与Node#sink_method只是查表见 node.rb而检测器的违规阶梯从不直接指名任何标签见 boundary_detector.rb所以加一个标签只意味着加一个表项。2. 把 reason 字符串预算改为可配置检测器的拒绝理由reason string里会引用来自流的类名——类名是攻击者控制的输出因此每条 reason 都是日志注入面。当前两个硬编码常量位于 boundary_detector.rbREASON_MAX_NAME_BYTES 96 # 名字截断阈值带显式 [truncated, N bytes] 标记 REASON_MAX_NAMES 8 # 名字列表上限超出显示 , and N more任务是把它们挪到Limits上lib/marshalsea/marshal/limits.rb 是承载全部 14 个资源上限的现有类可作为挂靠点让行预算较小的日志系统可以收缩它们。决定成败的对抗性测试是一个含换行符的 10,000 字节类名无论你把上限设在哪里都必须产出一行、被截断、经inspect转义过的 reason——换行符不能借机伪造日志行。记住措辞这是日志注入面不是排版偏好。3. 教扫描器一个额外的链接方法扫描器的入口点表 scanner.rb 中to_s和coerce被分类为GATE_LINK——即“链条中途可被 gadget 触达、但反序列化器绝不会派发的方法”其formats: []表明它不属于任何格式的入口to_s { gate: GATE_LINK, arity: 0, formats: [] }, coerce { gate: GATE_LINK, arity: 1, formats: [] }从公开的 chain 文献中再找一个这样的链接方法以gate: GATE_LINK, formats: []加进ENTRY_POINTS然后确认链接计数links上升而可达计数reachable不动。如果 reachable 也动了说明你分类错了——而“分错链接与入口”正是 gadget 扫描假阳性的最大来源参见 02-ARCHITECTURE.md 对 53 links vs 140 entry points 的讨论。4. 给你关心的扫描加一个--namespace过滤器Scanner.new(namespace: Gem)已经存在in_namespace?的实现就在 scanner.rbdef in_namespace?(name) namespace.nil? || name namespace || name.start_with?(#{namespace}::) end而 justfile 的scan配方也已透传该参数所以just scan Gem开箱即用。用它只扫描你自己应用的命名空间再与全量扫描对比——通常最有意思的结果是你自己的类几乎都不是候选候选几乎全在依赖里。中级Intermediate向 CVE 写链、追踪可达性这五个题开始触碰“攻击侧”与“防御侧”的深层逻辑。1. 针对另一个 CVE 编写第三条链链条注册是目录即身份的往lib/marshalsea/chains/丢一个文件让它继承Baseinherited钩子会自动注册见 chains.rb 的Dir[...]加载与Chains.register以及 chains/base.rb。你需要提供metadataname、vector、cve、gem、受影响版本约束affected constraints、kindgenerate返回一个活的 object 或 documentserialize仅当默认的Marshal.dump对你的入口不正确时才需要。原文档点名的合理目标是RDoc 的 CVE-2024-27281机制有据可查、受影响区间精确。真正的陷阱是——四个“已修复”版本里包含错误的修复所以你的affected约束必须写成6.3.4.1而不是6.3.4。这与仓库里erb-def-module链的做法完全一致它断言6.0.1affected、6.0.1.1不 affected见 chains/erb_def_module.rb 与 02-ARCHITECTURE.md 的版本边界表。验收方式就是双向版本边界测试。2. 让扫描器把链接追踪成真实链当前扫描器把入口点entry points和链接links作为两个并列的列表报告从不把它们连起来。这是诚实的做法但离真正的问题差一步给定这个入口点它能触达什么任务构建第二遍分析——对每个可达入口点用 Prism 走读方法体查找对 ivar 持有接收者的方法调用输出候选的两步链。诚实的交付物不是“链查找器”而是一份带排序的列表加上对假阳性率的书面说明——因为静态走读无法知道这些 ivar 在加载时到底装了什么。用仓库里那条已知为真的链来实测这个比率。参考实现基础在 scanner.rb 的state_reference?/parse_definitionsPrism 解析、按source_location行号找DefNode、递归问子节点。3. 关掉 LoadGuard 的#hash盲区而不付出 strict 模式的代价LoadGuardlib/marshalsea/marshal/load_guard.rb默认不监视#hash与#eql?因为它们是 Ruby 里最热的方法strict: true会监视它们并为此付费。要找到第三条路监视#hash但在做任何其他工作之前先按接收者所属类是否在允许集合之外过滤TracePoint或改用TracePoint#enable(target:)来限定追踪范围而不是在 handler 内部过滤。然后必须测量在真正关心的 payload 尺寸上测并带着 payload 尺寸发布数字。如果你的版本并不比strict: true便宜多少那也是一个真实结果应当如实写下来——仓库自身的纪律就是“不带 payload 尺寸的倍率数字不算数”参考 LoadGuard 在 45 字节 session cookie 上 40.4x、在 488 KB 文档上 1.0x 的公开数字。4. 构建认证会话信封当前靶标直接反序列化一个完全没有签名的 base64 cookie——对教学靶标而言真实但绝非真实应用该有的样子。任务在Marshal.load前面加一个 HMAC 或 AEAD 信封让字节在任何反序列化器之前先经过验证然后写那个让教训落地的测试有效签名并不能让 payload 安全。用正确的密钥给一个真实的 gadget payload 签名确认它仍然执行。这就等于在你自己的代码里复现了 CVE-2019-5420 和 CVE-2018-15133签名解决的是篡改tampering不是不可信来源untrusted origin——一个持有密钥的攻击者生产并由你签名的载荷安全性分文不增。原文档特别强调当前实验室刻意不做这一层不是疏忽去掉密码学层之后“两条 allowlist 为何一成一败”的教训更清晰。5. 把 Psych inspector 扩展到 OjRuby 的ojgem 在:object模式下支持从 JSON 实例化对象而这是它的默认模式。“一个开箱即用的 JSON 解析器居然具备对象注入能力”是整个领域最反直觉的事实而本实验室目前对它零表达。任务写第三个 reader读取 Oj 模式 JSON 而不加载它报告它会实例化哪些类并用与另外两个 reader 相同的词汇表产出一个Decision。设计约束才是重点现有两个 readerMarshal 与 Psych刻意共享同一个Decision类如果第三个 reader 需要不同的形状那恰恰在告诉你这个抽象有问题。现有 inspector 的Reference/revival/key_position词汇表在 lib/marshalsea/psych/inspector.rb 中是扩展的直接参照。高级Advanced挑战设计边界五个题都踩在该项目刻意留下的、带明确取舍的缺口上。原文档点名其中两项是有记录的技术性延期documented deferrals带着真实取舍如实命名而不是伪装成练习题。1. 构建设计契约要求的 eager-load 阶段扫描器只能看到已经被 require 的类裸进程里是 691 个模块boot 起来的 Rails 应用里是几千个。显然的修法是加一个阶段在扫描前 require 每个已装 gem 里的每个文件——但它按设计就是任意代码执行require 一个 gem 会运行它的顶层代码所以对不可信 gem 运行 eager-load 就是把供应链武器对准操作员自己。要交付的不只是循环而是让它可以辩护的隔离独立容器、无网络、只读挂载、超时、以及一个帮助文本明确说明其后果的显式 opt-in 标志。真正重要的交付物是书面的威胁模型。原文档直言这是整个项目最大的未决项也正是因此被推迟。2. 建模第二个 Ruby 解释器版本解析器建模 Ruby 3.4 及更新版本gem 的版本底线是 3.4因为Marshal.load直到 3.4 才校验 bignum 符号字节。相关常量在 constants.rbBIGNUM_SIGN_POSITIVE BIGNUM_SIGN_NEGATIVE - BIGNUM_SIGNS [BIGNUM_SIGN_POSITIVE, BIGNUM_SIGN_NEGATIVE].freeze这导致解析器与解释器在 3.3 上不一致而just packagescripts/package-gate.sh每次运行都在两个方向上证明这个边界。任务让解析器感知版本——接受目标版本、在 3.4 以下放宽 bignum 符号检查、下调版本底线并写差分测试在两个容器里跑同一个流断言解析器与 3.3 和 3.4 的Marshal.load各自一致。你很快就会撞上真正的一课“这条流是为哪个 Ruby 写的”是流本身回答不了的问题。3. 攻击解析器的取证容错解析器刻意在 CRuby 停止的地方继续解析检测器则对记录下的异常一律拒绝——这个“解析器贴标签、检测器做裁决”的分裂见 02-ARCHITECTURE.md就是设计本身但它同时是攻击面解析器对格式的任何一处模型与 CRuby 行为不一致都是一个潜在的差分。任务去找一个。写一个模糊器生成流喂给解析器和沙箱化的真实Marshal.load标记出“解析器报告的类/汇集合被解释器实际行为反驳”的所有案例。原文档的定位很直接这是仓库里价值最高的安全工作哪怕只确认一个差分就是真实发现。4. 编写一个击败 denylist 的检测器01-CONCEPTS.md 用 26 个 picklescan 自身 CVE 和 CWE-184Incomplete List of Disallowed Inputs论证过对序列化流做 denylist 扫描输在架构上而不是执行上。认真对待它设计替代方案——结构性策略structural policy不基于名字列表而基于图的结构作裁决。一个只含原始类型、数组、键为原始类型的 hash 和字符串的流无论存在哪些类按构造就是安全的。把它实现为第四个策略测量它拒绝多少真实世界 payload——那个数字会很高而这正是诚实的代价——然后写下“按构造安全”与“有用”之间的边界到底在哪里。5. 把靶标变成真实的漏洞应用语料库靶标目前有四个端点、横跨两个反序列化器。任务加入 CVE-2022-32224 的二阶 sink——一条路由把攻击者影响的数据写入存储另一条不同的路由把它读出来并反序列化这样 payload 永远不会出现在触发执行的那次请求里。这正是能击穿“以 HTTP 边界画信任边界”那种威胁模型的形状而且比直连情形难推理得多。证明它的闸门阶段必须跨越两次请求——这本身就是一项值得拥有的构建物。终极挑战Capstone让载荷穿过检测器如果想用一个项目把整座实验室串起来就做这个。检测器的LIMITATION_NOTICE已经白纸黑字写明accept 决定只意味着“这些字节匹配了这条策略”。你的任务是把这句话变成具体的事实找到一条被 strict-allowlist 检测器接受、但仍会做操作员绝不认可之事的流。三个攻击角度都合法找一个值得放进 allowlist、却仍然危险的类。通知直接承认“类 allowlist 比较的是序列化后的名字它并不证明对应 Ruby 代码无害。”一个把ERB加进 allowlist 的应用就会接受那条已公开的 CVE 链——因为那个 payload携带零个 sink 标签验证Marshalsea::Chains::ErbDefMethod.canary(...)产出的 112 字节流sinks 0见 03-IMPLEMENTATION.md。再找一个具备同一性质的类。找一处解析器没有建模的派发。阶梯覆盖了 sink 标签、key 位置的#hash/#eql?、Range端点的#——五条规则里有三条是因为先有绕过发布才补上的02-ARCHITECTURE.md 记录了这个过程。没有任何理由认为列表是完整的去读marshal.c找第六个。找一处解析器与解释器的差分。如果解析器和 CRuby 对一条流的内容意见不一那么检测器裁决的是一张不会被加载的图。游戏的规则也就是项目拿来自我约束的规则你的绕过必须能从字节串复现而不是手工搭好的对象图——写不成字节就不是 payload它必须在strict_allowlist下被接受只在deny_sinks_only下得手不算数——击败弱策略证明不了任何事修复和 mutant 一起交付先加捕获它的规则再把规则抽掉看着语料条目变红。杀不死的规则就是没测过的规则同时加一条阴性对照形状相同但必须仍被接受的 payload——否则你无法把“新规则有用”和“检测器见啥拒啥”区分开。原文档对这件事的判词值得抄下来当你完成过一次之后你对这类 bug 的理解会超过任何文章的教学——因为你站在了同一个文件的两侧。下一步Where to go next带着 03-IMPLEMENTATION.md 的代码重读 01-CONCEPTS.md一旦亲眼看过in_hash_key_position如何把 payload 逐字节拼接起来“两条 allowlist 为什么一成一败”的论证读起来会完全不同一旦知道确认一个单一论断需要多少工作Equifax 辟谣读起来也完全不同。然后运行just corpus从那张表里随便挑一行去找到把它写进表里的那个测试——它就在 test/corpus_test.rb 与 test/support/adversarial_corpus.rb 之间而那条测试链的末端是你刚在热身题里亲手摸过的同一张SINK_METHODS表、同一个ENTRY_POINTS常量、同一条LIMITATION_NOTICE。整座实验室的设计意图就是用一轮轮“先写绕过、再补规则、再证明规则”的循环把你训练成能从两个方向看同一份文件的人。赞分享【免费下载链接】Cybersecurity-ProjectsBuilding 70 Projects ranging from beginner to advanced so anyone can — learn from, build upon, use as a reference, or even copy directly. Gamified Cybersecurity learning 项目地址https://gitcode.com/gh_mirrors/cy/Cybersecurity-Projects点击查看免费下载相关推荐Gadget Inspector自动化检测Java反序列化漏洞的利器Gadget Inspector自动化检测Java反序列化漏洞的利器 项目介绍 Gadget Inspector 是一个用于检测Java库和类路径中gadge探索Java反序列化漏洞的利器实验室版实战指南探索Java反序列化漏洞的利器实验室版实战指南 在复杂而安全挑战频出的技术舞台上Java作为应用广泛的语言其反序列化过程中的安全隐患不容忽视。为此我们聚marshalsea 反序列化安全实验室实战不解冻对象读懂 Marshal 与 YAML并构建真实 CVE-2026-41316 利用链marshalsea 反序列化安全实验室实战不解冻对象读懂 Marshal 与 YAML并构建真实 CVE 2026 41316 利用链 本文是 deser上一篇Confetti高级用法动态修改粒子参数实现创意动画效果下一篇Android旋转处理与状态保存ExpandableLayout的完整生命周期管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表