尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
marshalsea 架构深度解析:Ruby Marshal/YAML 反序列化防御库的组件设计与攻防边界
【免费下载链接】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点击查看免费下载本篇技术指南以marshalseaCybersecurity-Projects 仓库中的 Ruby 对象反序列化安全实验项目的架构设计文档为核心系统讲解其两大只读解析器Marshal Parser 与 Psych Inspector的构建原理、边界检测器的三种决策状态、七级违规阶梯、gadget 扫描器的分类学、payload 构建器自证不执行的设计约束、以及 TracePoint 运行时守卫的在方法体执行前否决能力。读完本文你将掌握如何在不复活任何对象的前提下描述攻击载荷、如何区分 gated 与 ungated 的派发路径并理解为什么Marshal.load的 allowlist 回调总是慢半拍而 Psych 的 allowlist 才是真正的边界。从一张架构图开始两个 Reader、一个词汇表02-ARCHITECTURE 文档开篇用一张图定义了整个库的形状这也是本文贯穿始终的主线——检测路径上没有任何一步会复活对象Marshal bytes ── Parser ── Node graph ──┐ ├── BoundaryDetector ── Decision YAML document ── Inspector ── Document ─┘ (proceed / blocked / observed) loaded classes ── Scanner ── entry points links (offense: what is usable) chain registry ── generate ── serialize (offense: build the payload) Marshal.load ── LoadGuard (TracePoint :call) (defense: veto before the body)进攻与防御两个半区永远不会互相调用进攻半区负责构建 payload 和寻找 gadget防御半区负责读字节并做决策。它们只在测试套件和 gate 处交汇——一方的输出恰好是另一方的输入。这是刻意为之的设计如果写检测器的人和写 payload 的人是同一个那么检测器只能抓到自己想象出来的 payload。因此项目存在第三个工件——对抗性语料库adversarial corpus进攻与防御两个半区都以它为标尺被衡量。架构文档PROJECTS/beginner/deserialization-gadget-lab/learn/02-ARCHITECTURE.md是概念篇01-CONCEPTS.md的设计落地而代码走读在 03-IMPLEMENTATION.md 中——读实现时请把这张图放在手边。Reader oneMarshal 解析器Marshal是二进制格式两个头部字节声明版本号之后是一棵带标签tagged的值树。解析器逐字节读取并构建节点图。三个绝不是它的底线绝不调用Marshal.load绝不把类名解析成真实类绝不从流中分配除字符串和整数以外的任何东西。实现见 lib/marshalsea/marshal/parser.rb其read_value是一个对标签字节的case分派构成全部文法。它认识的标签与三个汇sink解析器理解的标签其中标出了决定性的几个0 nil T true F false i fixnum l bignum f float string : symbol ; symlink object link / regexp [ array { hash } hashdefault S struct I ivar o object e extended C userclass c class m module M module (old) u userdef - SINK, dispatches Klass._load U usermarshal - SINK, dispatches obj.marshal_load d data - SINK, dispatches Klass._load_data三个标签是sinku、U、d。看到其中一个就等价于一个真命题——加载这条流会在任何 allowlist proc 运行之前对某个具体类名分派某个具体方法。这是解析器能产生的最强置信信号但也不够。已公开的 CVE 链条产生的 sink 标签数是零因为 ERB 没有定义marshal_load所以仅靠 sink 检测永远抓不到它这正是 test/support/adversarial_corpus.rb 与 target gate 反复验证的事实。sink 的映射集中在 lib/marshalsea/marshal/constants.rb 的SINK_TAGS/SINK_METHODS常量表中——要新增第四个 sink 标签只需添加一个表项而不是修改某个判定谓词。解析器是刻意取证式的这是全库最反直觉的设计决策。CRuby 在遇到实例变量名槽里放的不是 symbol时会直接 raise 并停止解析。而这个解析器继续解析把问题记录为一个具名异常named anomaly并且照样返回一张完整的图。同样 17 个字节两者行为完全不同Marshal.load: ArgumentError: dump format error for symbol(0x69) parser: class_names[Object] anomalies[instance variable name slot holds fixnum, not a symbol] detector: blocked: stream is not canonical Marshal: instance variable name slot holds fixnum, not a symbol, so Marshal.load refuses it and there is nothing here to permit如果Marshal.load反正会拒绝它为什么还要费这个劲因为解析器的目标是描述而不是准入控制。如果一条敌对流把 sink 藏在 symbol 该在的位置一个遇到第一个结构意外就 raise 的解析器只能报告 malformed——你什么信息都拿不到。一个继续解析的解析器会同时报告类名、sink和异常得到一份取证记录而非一条错误信息。严格性被上移了一层检测器把任何记录的异常当作立即拒绝而且理由字符串说得很精确——不是这很危险而是Marshal.load拒绝它这里没有可放行的东西。这两个论断不同只有后者为真。这一拆分是全库最重要的结构思想解析器打标签检测器做决策。它意味着两个组件对同一条流可以持有不同意见而这种分歧是特性。项目里有test_sink_in_an_instance_variable_name_position_is_still_reported这个测试专门把它钉死还有一份让解析器在角色槽上变严格的提案被否决——原因正是它会删除这个测试。同样的思路体现在浮点数解码上。旧版 Marshal 浮点会在 NUL 字节之后携带尾数扩展现代 Ruby 依然会读它canonical 1.5: \x04\bf\b1.5 legacy stream: value1.5 undecoded_tail\x00abcde fully_decodedfalse Marshal.load: 1.5000000000055356解析器不猜、不装懂。它解码能解码的部分把其余部分标记为undecoded_tail并诚实地回答fully_decoded?。一个悄悄返回1.5的读取器等于声称自己和解释器达成了它从未挣得的共识。float 解码的具体实现在 lib/marshalsea/marshal/float_body.rb。密封Sealing解析完成后整张图被深度优先地冻结frozen包裹它的Result也被冻结。解析结果是一份对已经发生的字节的不可变描述。下游任何组件——包括从Decision#result拿回它的调用方——都无法篡改某个策略决策所依据的记录。parse的结尾正是root.each(:seal)后返回冻结的Result见 parser.rb。Reader twoPsych InspectorYAML 不需要手写解析器因为 Psych 自带一个不复活任何东西的解析器。Psych.parse_stream构建一棵Psych::Nodes::*的 AST 然后停手。Inspector 遍历这棵 AST。它提取的内容是每个!ruby/*标签对应的一条Reference携带三样东西!ruby/object:Gem::Version - class_name: Gem::Version kind: object revival: init_with - what Psych WOULD call key_position: false标签种类 → 复活方法的映射表是 Inspector 的全部价值所在因为它把这个文档提到了一个类翻译成这个文档会在那个类上分派init_with。映射在 lib/marshalsea/psych/inspector.rb 的Tags::REVIVAL_METHODS常量中标签种类Psych 会分派的方法!ruby/objectinit_with!ruby/array、!ruby/string、!ruby/struct、!ruby/exceptioninit_with!ruby/hash[]!ruby/marshalablemarshal_load别名只计数、绝不展开。一个别名炸弹alias bomb检查起来零成本——这正是要点一个为了报告而展开别名的 inspector等于把它要预警的 DoS给引了进来。计数是有界并会被报告的因为展开成本最终仍由真正的 loader 承担。Inspector 的局限声明LIMITATION_NOTICE对自己的位置直言不讳这段话值得反复读Unlike Marshal, Psychs own allowlist is a real veto: Psych checks the tag before it revives the object, where Marshal runs its proc after the callback has already fired. Same intent, opposite outcome, decided entirely by where the check sits. That meansYAML.safe_loadwithpermitted_classesisa boundary and this inspector is only detection and reporting on top of it.一个把自己定位成safe_load替代品的检测器卖的是降级。这个项目在常量里就把它说清楚了。从实现看Walk遍历 AST 时用index % MAPPING_KEY_STRIDE 0判断 key 位置Mapping 的 children 是 key/value 交替的扁平列表从而能说出这个类在 mapping key 上所以重建映射时它的#hash和#会运行——这等价于 Marshal 侧的 hash-key 规则也是violation_for里第一个检查的原因。决策对象三种状态没有accepted?三种状态构造上互斥proceed the bytes matched the configured policy blocked the bytes violated it, and reason says which rule and why observed the bytes violated it, the reporter was called, and the caller was handed the snapshot anyway (monitoring mode)不存在accepted?谓词而且它的缺席是故意的。在observe_and_log下策略是否放行与这条流是否干净是两个不同的答案一个名为accepted?的单一谓词无法同时回答两者。写出Marshal.load(d.snapshot) if d.accepted?的调用方会在监控模式下悄悄加载被报告过的所有东西。强迫调用方点明他们真正想表达的状态值得多写的这六个字符。状态在构造函数里对照冻结列表校验非法状态在构造时抛ArgumentError而不是得到一个到处静默返回 false 的谓词。决策对象实现在 lib/marshalsea/marshal/boundary_detector.rb。违规阶梯The violation ladder检测器按固定顺序检查规则返回它找到的第一条违规。顺序不是随意的——从这甚至不可加载到这在加载中会派发再到它提到了你没批准的类1. role anomaly the parser recorded a malformed slot - Marshal.load refuses it anyway 2. sink tag u / U / d - _load, marshal_load, or _load_data dispatches 3. hash-dispatching key #hash runs when the Hash is rebuilt 4. eql?-dispatching key #eql? runs as soon as two keys collide 5. Range endpoint # runs when Range#marshal_load validates its ends ────────── deny_sinks_only stops here ────────── 6. non-canonical version declares a Marshal version no real Ruby emits 7. unapproved class name strict_allowlist only规则 15 是对派发的陈述无论你信任哪些类它们都为真所以它们在包括deny_sinks_only在内的每种策略下都会执行。规则 6、7 是策略只有strict_allowlist会执行它们。规则 3、4、5 的存在是因为有三个曾随版本发布、后来才被发现的绕过。三个绕过当时都同时通过了deny_sinks_only和把类加入白名单的严格模式——因为两条规则都没有从 key 位置看派发一个String子类 hash key 在桶冲突时到达#eql?一个 gadget 嵌套在作为 key 的裸Array里。19 字节\x04\b{\x06[\x06o:\fUngated\x00i\x06一个端点会派发#的Range。这正是阶梯被实现为数据而非一串临时条件分支的论据每条规则都是关于解释器会做什么的独立、可单独测试的论断且语料库为每一条都带有一个 payload。test/support/adversarial_corpus.rb 中HASH_KEY_SHAPES_THAT_DISPATCH、HASH_KEY_SHAPES_THAT_DISPATCH_EQL_ONLY、RANGE_WITH_OBJECT_ENDPOINTS等正是这些 payload。理由字符串是攻击者可控的输出理由字符串会引用类名而类名来自流。因此每条理由字符串都是日志注入面在输出前要过三关名字在 96 字节处截断带显式[truncated, N bytes]标记而不是静默截断列表最多展示 8 个名字带显式的, and N more所有内容在二进制强制的字节上走String#inspect所以一个包含换行符的类名无法伪造日志行。一个把无界攻击者可控字符串糊进日志的检测器等于把防御变成了投递机制。这些常量在boundary_detector.rb里直接可见REASON_MAX_NAME_BYTES 96、REASON_MAX_NAMES 8、REASON_TRUNCATED、REASON_ELIDED_NAMES。Scanner狩猎已加载的类图Scanner 回答的问题和检测器不同不是这条流危险吗而是当前进程里已加载的哪些类可以被用作 gadget。它遍历ObjectSpace.each_object(Module)对每个具名模块收集该模块自己定义非继承的自动调用方法然后逐条打分。实现见 lib/marshalsea/scanner.rb。分类学是一张表不是一串方法名概念篇的 gated-versus-ungated 轴在这里变成代码。每个候选方法携带三个事实方法门Gate反序列化器提供的参数个数格式Formatsmarshal_loadgated1Marshal, Psych_load_datagated1Marshal_loadgated (singleton)1Marshalinit_withsoft1Psychhashungated0Marshal, Psycheql?ungated1Marshal, Psychungated1Marshalungated1Psych[]ungated2Psychmethod_missingungatedvariadicMarshal, Psychrespond_to_missing?ungated2Marshal, Psychrespond_to?ungatedvariadicPsychto_slink0nonecoercelink1none这张表在scanner.rb的ENTRY_POINTS常量中逐字实现。Gate 列来自 01-CONCEPTS.md 的轴gated 方法需要一个诚实的respond_to?才被调用ungated 方法被盲目派发。Marshal.load对marshal_load/_load是 gated 的先问respond_to?(m, true)注意true意味着私有方法也算对hash/eql?/是 ungated 的。Format 列是为什么入口点按格式而非全局打分一个方法擦除的代理method-erased proxy是合法的 Psych 入口点、却是非法的 Marshal 入口点。今天的扫描在 Marshal 下找到 29 个入口点、在 Psych 下找到 33 个两个集合互不包含。同一个类经由 gated 路径死掉、经由 ungated 路径点火——这是扫描器一切行为的根基。Arity 列是最微妙的一列。一个名为init_with但接收三个参数的方法不能被只提供一个参数的反序列化器调用报告它是误报。在干净镜像上arity 检查恰好拒绝一个候选而它正是最好的一个entry points whose arity cannot accept the deserializers call: 1 Psych::Visitors::ToRuby#init_with arity3 needs1那是 Psych 自己的 visitor 方法恰好和它分派的钩子同名。没有 arity 列它会永远挂在每次扫描的顶部成为一个永久性、令人困惑的误报。Link 行是没有格式的那些。to_s是已公开通用链中的真实一步但Marshal从不调用它。它是gadget在链条已经开动之后调用的方法不是反序列化器用来启动链条的方法。混淆两者是 gadget 扫描中最大的误报来源所以 link 被收集、计数并从可达性中排除。今天的扫描在 140 个入口点旁边找到 53 个 link。可达性以及它刻意拒绝得出的结论把分类学加起来reachable? is an entry point (not a link) AND its arity can accept the deserializers call AND ( it is gated or soft-gated - the tag alone reaches it OR its body references object state OR its source could not be read ) - read that last one twice最后这个子句最有趣。要判断#hash这样的 ungated 方法是否有趣Scanner 用 Prism 解析方法源码问方法体是否引用了实例变量或做了无接收者的调用。返回字面量的#hash是惰性的读name的#hash是潜在的枢轴pivot。但如果源码读不到Scanner 把该方法判为可达而非惰性——对安全工具来说这才是正确的出错方向。在干净镜像上有 8 个候选处于此状态扫描结果会如实说明。少报under-reporting要被大声说出来一个静默吞错错的 gadget 扫描器比没有扫描器更糟因为它产出一份干净的短清单读起来像没什么可看的。所以每个被吞掉的错误都被计数并归因到具名站点suppressed errors (this scan under-reports): source_parse 3 142 candidates have no Ruby source and were never analysed; the reachability filter does not cover them共有五个抑制站点其中三个被标记为lossy——在那里失败意味着候选从未被创建。Report#candidates_lost?区分这次扫描略不精确与这次扫描整体缺项两种状态complete?与fully_analysed?回答两个独立问题而非一个模糊问题。实现见scanner.rb的SITES、LOSSY_SITES和Report类。干净ruby:4.0-slimRuby 4.0.6上的头条数字2026-07-31 重测modules691 candidates193 entry_points140 links53 gated11 soft5 ungated124 reachable43 (28 of them ungated) marshal29 psych33 unanalysable142 unreadable8 suppressed3这些数字不是关于 Ruby 的事实。ObjectSpace报告不了还没人 require 的类所以这是这个特定进程加载了什么的陈述。requireactive_support会让它们全部移动。请自己重跑而不是引用它们just scan即可。诚实的表述在项目里写明了gadget 发现工具告诉你今天、在这个进程里存在哪些链。它不是一种控制。三条链目录即身份三个 payload目录就是身份。lib/marshalsea/chains/下的每个文件都继承BaseBase.inherited自动注册它。不存在中央注册表文件——注册表文件是那种有人加了链却忘了更新它就会腐烂的东西。erb-def-method primitive CVE-2026-41316 def_method erb-def-module chain CVE-2026-41316 hash psych-init-with chain none init_withPrimitive 与 chain 是不同的标签而差异是承重的。chain在反序列化器内部触发无需应用配合primitive伪造一个对象穿过守卫保持惰性直到应用拿它做了点什么。把erb-def-method标成 chain 是夸大其词删除它则删掉了危险调用点可以活在你自己的代码里这堂课。每条链把受影响的版本声明为真实的Gem::Requirement约束见 base.rb 的affected_requirements/affects?于是边界可查询而非散文erb 4.0.3 affected? true erb 4.0.3.1 affected? false erb 6.0.1 affected? true erb 6.0.1.1 affected? false erb 6.0.4 affected? falseerb-def-module通过 hash-key 位置进入。很自然的构建方式是Marshal.dump({ proxy 1 })——但那会在你构建字面量时就对 proxy 调用#hash在你本该为别处生成 payload 的进程里本地引爆链。修复办法是永远不把对象放进 hash。Base#in_hash_key_position独立 dump 对象、切掉它的两个头部字节、把主体拼进手写的一入口 hash 帧Marshal.dump(nil) \x04\b 0 ^^^^^^ take the header Marshal.dump(proxy) \x04\b body ^^^^^^ take the body result \x04\b {\x06 body 0 ^^^^^^ ^^^ nil value one-entry hash frame这个拼接只有一种出错方式而代码选择拒绝而非冒险。Marshal 对象链接是位置性的6意为第六个已注册对象。把主体拼到 hash 节点后面会让每个索引偏移一位所以含有回引用的 payload 图会被静默解码成与你构建时不同的图。拼接之后构建器用库自己的解析器解析自己的输出如果任何对象链接存活就抛ObjectLinkRefusedErrorbase.rb。能产生自己没想过的图的 payload 生成器比拒绝产出的更糟。这也是一个漂亮的闭环进攻半区用防御半区的读取器验证自己。运行时守卫在方法体运行前否决LoadGuard做到了 allowlist proc 做不到的事它在方法体运行之前否决。对:call事件的TracePoint在方法入口触发所以从 handler 里 raise 意味着方法体永不执行。Marshal.load │ ├─ allocate Klass ├─ dispatch marshal_load ── TracePoint :call fires HERE │ owner not permitted - raise │ (body never runs) └─ r_post_proc ── your allowlist proc would have run HERE, too late钩子列表靠枚举推导而非猜测。在加载一个同时包含普通对象、marshal_load类、_load类、Struct、extended 对象、Hash/Array/String 子类、自定义#hashkey、Range、Time、Rational、Regexp和Exception的 payload 时追踪每一次:call与:c_call得到完整派发面ALL distinct method_ids seen: [:_load, :hash, :initialize, :load, :marshal_load]很小——这正是守卫可驾驭的原因。默认钩子集是GATED_HOOKS DISPATCH_HOOKS即marshal_load、_load、_load_data、method_missing、respond_to_missing?见 lib/marshalsea/marshal/load_guard.rb。一个结构性规则撑起全部守卫绝不在它正在检查的接收者上派发任何方法。它通过Object.instance_method(:class)、Object.instance_method(:is_a?)、Module.instance_method(:name)解析 owner 的名字绑定bind到接收者而不是在它上面调用。为什么因为接收者是 gadget——方法擦除的代理通过method_missing回答.class和.is_a?而method_missing正是已发布链条的入口。一个问对象你是谁的守卫会在它正要否决的链里点火而且发生在不追踪自身嵌套调用的TracePointhandler 内部。03-IMPLEMENTATION 文档里有那次失败的完整记录和钉死它的测试test_the_guard_never_dispatches_a_method_on_the_receiver_it_inspects。它自带绕过包括一个默认就留着的洞默认钩子集监视marshal_load、_load、_load_data、method_missing、respond_to_missing?不监视#hash和#eql?。这是一个故意的、被文档化的洞。同一个 19 字节 payload 的三方对比default guard - LOADED, #hash fired, watches?(:hash)false strict guard - blocked: deserialization hook OKey#hash is not permitted, #hash never fired detector - blocked, nothing loaded at all默认守卫放行了 ungated key 形状。为什么留着这个洞因为#hash和#eql?是 Ruby 里最热的方法监视它们会彻底改变成本与误报画像。strict: true关掉它并接受成本。检测器在加载任何字节之前就能抓到同一形状——那才是更便宜的抓点守卫的局限声明明确指向它。其他局限同样写在声明里而非脚注里它只覆盖加载窗口。一个不携带任何钩子的类会被自由实例化并在应用之后触碰它时触发。这超出了任何守卫能看见的窗口——这正是erb-def-methodprimitive 的运作方式。它是线程作用域的。另一个线程上的加载不在覆盖范围。它的成本不是乘数。启用一个TracePoint大约每次加载多花 46 微秒近于常量所以比值由加载本身做了多少功决定488 KB 文档 1.0x、46 KB 文档 1.1x、142 字节会话 20.7x、45 字节会话 cookie 40.4x。会话 cookie 正是本实验反序列化的东西所以这个数字是带着 payload 尺寸一起公布的。项目早期研究记录过单次大 payload 测量的1.4x该数字如今带着注明日期的更正——不带 payload 尺寸引用一个比值读起来像是对它不应得的背书。靶标Target一对端点差一个 curl一个基于 Rack 3 的 Sinatra 应用容器内没有通向宿主机的路由、根文件系统只读、capabilities 被丢弃、no-new-privileges、有 pids 限制、只有一块可写的tmpfs给 canary 文件。Dockerfile 见 target/Dockerfile四个端点的实现见 target/app.rb。四个端点两对镜像配对所以差异只是一个curl的距离POST /session issue a benign session cookie GET /render Marshal.load, then compile the template VULNERABLE GET /render/safe inspect the stream first, then load DEFENDED GET /yaml/unsafe YAML.unsafe_load the same session VULNERABLE GET /yaml/safe inspect, then YAML.safe_load DEFENDED GET /canary report whether the canary file exists配对是概念篇的功课变成可执行代码/render和/yaml/unsafe用同一个 ERB 对象都达到代码执行/yaml/safe在复活之前按标签拒绝它因为 Psych 的 allowlist 是真否决/render/safe只能检查字节然后祈祷因为 Marshal 的不是。注意/render/safe在检测器放行之后做了什么它仍在rescue里调用Marshal.load仍检查结果确实是一个会话 hash然后才编译。三层因为第一层明确不是边界。这个三层检测→防复活加载→结构校验在 app.rb 的/render/safehandler 中逐行可见。靶标 gate 从同一内网上的第二个容器驱动这一切而不是从宿主机端口。--internalDocker 网络会屏蔽已发布的端口所以一个想从宿主机curl的 gate 要么失败、要么逼你把网络改成非隔离的。从网络内部攻击靶标让出网隔离保持真实scripts/target-gate.sh 的docker network create --internal与全部容器参数展示了这一点。限制十四条轴默认全开解析器约束十四个独立资源每一条除非你主动退出否则强制执行stream bytes nodes registered objects symbol definitions collection entries scalar bytes total scalar bytes object links symbol references symbol name bytes class name bytes instance variables struct members nesting depth两个设计注记。限制默认开启、可退出而不是默认关闭、可选入——因为最需要它们的调用方正是那个从没读过这篇文档的人。以及Limits.permissive依然钉住max_depth递归下降解析器里的无界递归是栈溢出而非慢解析宽松不该意味着让进程崩溃。全部默认值在 lib/marshalsea/marshal/limits.rb 的DEFAULT_MAX_*常量中且解析器在分配之前就失败DepthLimitError: exceeded depth 4 LimitExceededError: stream bytes 110 exceeds 8关键点budget 在消费点被计费。read_counted_bytes先scalar!(size)再take(size)所以一条声明 900 MB 字符串的流在声明处就被拒绝而不是分配之后。Gate六个阶段79 条断言六个阶段just gate运行79 条断言全部必须通过阶段它证明什么check七个测试套件加独立控制脚本matrix链条在哪些 Ruby 版本上触发——在每种版本里运行它exploitCVE 边界双向验证在脆弱镜像上触发、在已修补镜像上被阻断只差一个docker pulldetector对抗性语料库每个 payload 以及检测器的决定target真实 HTTP 上的端到端利用外加隔离、错误泄漏、sink 标签检查package构建产物、manifest 审计、安装路径、发布身份、负控制每个阶段都携带一个它必须拒绝的输入。只有正向用例的 gate 无法发现一个永远说是的检查器。检测器语料库是最清晰的例子因为接受与拒绝在同一张表里object_in_value_position_control accept Foo hash_key_object reject Foo stream puts Foo in a hash key, so its #hash runs d hash_key_object_link reject Foo stream puts Foo in a hash key, so its #hash runs d hash_key_struct reject Foo stream puts Foo in a hash key, so its #hash runs d hash_key_extended reject Foo,Comparable stream puts Comparable in a hash key, so its #hash hash_key_user_class_over_array reject Foo stream puts Foo in a hash key, so its #hash runs d hash_key_bare_array reject Foo stream puts Foo in a hash key, so its #hash runs d hash_key_nested_bare_array reject Foo stream puts Foo in a hash key, so its #hash runs d hash_key_user_class_over_string reject Foo stream puts Foo in a hash key, so its #eql? runs d hash_key_extended_over_string reject Comparable stream puts Comparable in a hash key, so its #eql? hash_key_regexp accept hash_key_array_of_primitives accept hash_key_empty_array accept range_with_object_endpoints reject Foo,Range stream puts Foo in a Range endpoint, so its # r range_with_primitive_endpoints accept Range读接受用例要和读拒绝用例一样仔细。hash_key_regexp、hash_key_array_of_primitives、hash_key_empty_array都是必须不被拒绝的 key 位置 payload它们阻止 key 位置规则退化成拒绝任何 key。这些形状来自 adversarial_corpus.rb 的HASH_KEY_SHAPES_THAT_DO_NOT_DISPATCH与hash_key_regexp等拒绝用例一一对应。object_in_value_position_control是最锋利的一个。同一个类、同一条流放在值位置而非 key 位置必须被接受。没有它一个简单拒绝任何提到Foo的流的检测器就能通过表中所有拒绝用例。还有一个值得命名的细化因为它花了一个真实 bug 才学到两个都拒绝同一输入的检查会互相开脱alibi。如果一个控制被标签检查和类检查双双拒绝废掉其中任何一个 gate 都还是绿的。所以 target gate 携带一个Inspector 批准、Psych 仍拒绝的 YAML 文档一条带别名的文档见 target-gate.sh 中inspector-approved, Psych-refused部分专门把两层隔离开。读下去03-IMPLEMENTATION.md 逐行走代码字节进节点图、节点图进决策、活对象进 payload以及 ActiveSupport 代理链端到端作为压轴。而 04-CHALLENGES.md 是扩展路线新链、跟随 link 进入真实链的扫描器、关闭守卫的延迟执行绕过以及让你用当前被接受的 payload 攻破这个工具的压轴题。赞分享【免费下载链接】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点击查看免费下载相关推荐如何用Bootswatch魔改Bootstrap主题配色_variables.scss变量定制完全指南如何用Bootswatch魔改Bootstrap主题配色_variables.scss变量定制完全指南 想给 Bootstrap 项目换上全新皮肤却不想前端UI组件设计系统MessageThrottle安全指南防止消息节流被滥用的完整教程MessageThrottle安全指南防止消息节流被滥用的完整教程 MessageThrottle是一个轻量级的Objective C消息节流和防抖库它通过ysoserial漏洞响应演练模拟反序列化攻击事件处理ysoserial漏洞响应演练模拟反序列化攻击事件处理 1. 事件背景与影响评估 1.1 漏洞背景 Java反序列化漏洞Unsafe Deserializa应用安全渗透测试上一篇如何在Django Admin中集成Django-ColorField5分钟快速上手下一篇终极指南如何使用Angular拖拽列表库实现高级嵌套列表功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

GNN与GCN工程实战:消息传递、变体选型与数据建模排查

GNN与GCN工程实战:消息传递、变体选型与数据建模排查

简介:这是一份关于图神经网络(GNN)的深度讲解PPT,共100页,面向希望系统入门图神经网络的研究生、算法工程师及对非欧式数据建模感兴趣的开发者。内容从“为什么需要图神经网络”切入,先厘清欧式数据与非欧式…

📅 2026/9/29 2:24:20
FastExcel捐赠Apache而非跑路:孵化流程、坐标迁移与Java Excel库的治理升级

FastExcel捐赠Apache而非跑路:孵化流程、坐标迁移与Java Excel库的治理升级

1. 仓库停更、Issue 没人回,FastExcel"跑路"的传闻从哪来的1.1 一次例行依赖检查发现的异常上个月我在整理一个老项目的依赖清单,顺手打开 FastExcel 的 GitHub 仓库想看看有没有新版本可以升级。结果邮件通知里那封 release 订阅信的日期还停…

📅 2026/9/29 2:24:20
VT-System配置实战:从板卡选型到CAPL脚本的硬件在环测试指南

VT-System配置实战:从板卡选型到CAPL脚本的硬件在环测试指南

简介:Vector VT-System中文使用手册是一份面向汽车电子测试工程师的官方技术资料,围绕Vector CANoe软件与VT系列板卡构成的硬件在环(HiL)测试环境,系统讲解从系统组成、板卡选型到安装连接与调试排错的完整流程。手册为…

📅 2026/9/29 2:24:20
MORE NEWS

更多资讯

📰

记忆张量亿元Pre A融资后,AI记忆如何成为Agent的长期基础设施

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

📰

技术写作者的多元变现之路:用 TaoToken 统一 Key 打通 AI 工具链的实战配置

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

📰

Spring Boot 读写分离实战:AbstractRoutingDataSource + ThreadLocal + AOP 动态路由

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

📰

OpenClaw 对接淘宝商品 API 做全天候选品监控:TaoToken 统一 Key 配置与 Python 定时任务实操

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

📰

无需代码!OpenClaw 2.7.9 本地电脑自动化工具完整搭建教程(含安装包与 TaoToken 配置)

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

📰

AI测试实战:Claude接入蓝湖MCP,联动Pycharm实现自动化

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬