区块链

Zodiac 在 Gnosis Pay ERC‑1271 漏洞中把四字节回滚数据判成了授权

旧包 @gnosis.pm/zodiac 3.4.0 引入 ERC‑1271 调用状态回归;代表性 Delay 锁定且仍受影响的是 3.4.1;改名后的 v3 首修为 v3.0.2;截至 2026 年 7 月 27 日,当前 4.3.0 是 SOSEC 对新部署与迁移的默认评估目标;处置同时暂停旧签名入口、闭合旧队列,并用正常返回与回滚魔数样本验收。

技术调查台上叠放着 Zodiac、Safe 与 ERC-1271 三层透明调用板,一条红色失败路径连接到排队和执行两笔交易的时间轴。
文章导航

研究依据SOSEC 区块链安全研究 · 固定 Zodiac、Delay、Safe 与 Gnosis Pay Account Kit 源码,逐字段复核 Gnosis Chain 交易、历史状态、调用轨迹与未验证运行时代码

来源Gnosis 事故复盘 / Zodiac、Safe 与 Account Kit 固定源码 / Gnosis Chain 交易、状态与运行时代码 / SOSEC 字段级复核

1 当前动作先于案卷细节:停旧入口、查旧队列、部署可验证的新运行时

处置结论:已启用的 Roles 或 Delay 只要能到达受影响的合约签名检查,或运行时代码来源尚未核清,就进入处置范围。立即暂停产生旧式附加合约签名的托管 API、relayer 与后台任务,逐实例读取模块实现和全部未决队列。每个候选制品都要经过本地兼容性、可复现构建、链上 runtime 与正负样本验收。

四个字面对象按顺序工作:signer/module Safe 提供合约签名身份 → Zodiac checker 把调用结果解释为授权或拒绝 → Delay queue 保存已经作出的决定并开始计时 → asset/card Safe 持有资产并执行到期操作。代表性路径中的 signer Safe 与 asset Safe 是两只不同账户;前者参与认证,后者承担保管和最终执行。

Solidity 低级调用一次返回两个值:status 说明子调用是否正常完成,data 保存正常返回数据或回滚数据。事件中的恶意 validator 以 REVERT(0,4) 结束,同时让回滚数据等于 ERC‑1271 成功魔数 0x1626ba7e。安全谓词必须同时要求 status=true 与魔数匹配;数据内容无法改写同一帧已经失败的控制流事实。

signer Safe 提供签名身份,Zodiac checker 作出授权判断,Delay 保存并计时,另一只资产 Safe 通过 MultiSend 执行已排队付款。
图 1:signer Safe → Zodiac checker → Delay queue → asset Safe。身份判断、时间持久化与资产执行属于不同对象。

Gnosis 复盘记录,2026 年 6 月 1 日的攻击转出约 149.6 万美元,另有约 30 万美元处于攻击者无法支配的状态;5,281 个余额不低于 1 美元的钱包合计暴露约 180 万美元。团队在 06:17 UTC 观察到首笔大额未授权转账,08:06 UTC 确认根因,随后暂停相关服务、修补模块、分批恢复账户,并承担损失、恢复用户余额。公开结果回答了事件规模与用户结局,逐实例模块替换和队列处置仍要由运营方保存链上证明。

这段谱系包含两个编号世代,单独比较“3.4.1”和“3.0.2”的数字会颠倒时间与安全结论。v4 从早期公开标签起已包含等价状态检查,当前 4.3.0 继续保留。资产清单把包命名空间、标签、提交、模块实现与 runtime hash 组成一个版本身份;运营决策读取实际链上代码,再使用标签解释来源。

本案的决定性状态变化发生在资产转移之前。Zodiac 把失败帧解释为有效 module signer,Delay 随即把操作写入队列;冷却结束后的执行函数只核对队头、操作哈希和时间,不再调用原 signer。源码修复只能阻止新误判,旧运行时已经写下的项目仍可能执行。因此,补丁、旧队列、账户权限和财务恢复是四项并行收口对象。

2 一次失败帧怎样成为队列项 11,并在时钟打开后移动资产

以下交易对是全文唯一的权威事件链。2026 年 6 月 1 日 08:29:05 UTC,交易 0x5f562057e1963964fc3a6d8b105057f3ab9f9dd7091ff5bdd8d51453f51bd456 到达 Gnosis Chain 上的 Delay 0x7ECf01F102c00C51DF095e9f6C4ce81F4CA878f3。选择器 0x468721a7 对应四参数 execTransactionFromModule(address,uint256,bytes,uint8);规范 ABI 调用之后还附有 Safe 签名字节、32 字节 salt 和外层 r/s/v

历史 Account Kit 提交 51f60789df3252c06aab963654a183f25abc6afdencodeDelayModTransaction() 第 43—52 行生成含选择器的完整调用,encodeERC1271Signature() 第 72—92 行再附加合约签名尾部。外层 r 指向 signer/module Safe 0xce7d2f8e54e1104ed7a4c1f88dac63aa4a825561;它是 Delay 已启用的模块,也是 Zodiac 要求作出 ERC‑1271 裁决的身份。

该 Safe 使用 SafeL2 1.4.1 实现 0x29fcB43b46531BcA003ddC8FCB67FFE91900C762 和 Safe4337Module v0.2.0 fallback handler 0xa581c4A4DB7175302464fF3C06380BC3270b4037。handler 把新版选择器转换成 legacy overload 并生成 SafeMessage;Safe 第一份合约签名槽指向候选 0x5A77953CAa27eD4638F4DfdC665b8064D0e97A35。固定区块 46470631 的四地址 owner 集合不含该候选。Safe 在第 315 行调用候选,候选回滚,后续第 331 行 GS026 成员与排序检查没有到达。

queue transaction:
  attacker EOA
    → Delay / inherited Zodiac checker
      → signer Safe 0xce7d…5561
        → Safe4337Module handler → Safe.checkSignatures(threshold=1)
          → candidate 0x5a779…97a35
            ↳ REVERT, data = 0x1626ba7e
      ↳ low-level result: success=false, returnData=0x1626ba7e
    → vulnerable checker accepts signer Safe
    → consumed[signer][moduleTxHash] = true
    → Delay stores queue item 11
低级调用同时送出成功状态和返回数据;失败支路仍可携带四字节数据,授权判断必须读取两者。
图 2:EVM 提供状态与数据两路结果;受影响谓词只接收数据,失败支路因而进入授权出口。

Zodiac 恢复的是 signer Safe 0xce7d…5561,随后 moduleOnly 把 ModuleTx digest 标为已消费。Delay 函数体又在索引 11 保存 keccak256(abi.encodePacked(to,value,data,operation)) 与创建时间,并推进队尾。此时批次字节已经描述 GNO 与 EURe 调用,收据却没有对应的 token Transfer;资产移动权以带时钟条件的队列状态存在。

08:53:45 UTC,交易 0x4d3888cf3c68bffb2013001aabaf7b3596fc5d3f5d4cc5f139487d8a4140ae14 调用同一 Delay 的 executeNextTx(),四项操作字段复现索引 11 的队列哈希。Delay 的 owner、avatar 与 target 指向资产/card Safe 0x0cae88383f2d1f308292e58cd71984bb6120e03d。执行路径经这只资产 Safe 以 DELEGATECALL 进入 MultiSend 0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526,最终从资产 Safe 转出 17.855442968223287575 GNO 与 364.071530831603544366 EURe。

一笔交易先把错误授权写入索引 11,180 秒后队列开放,1,480 秒后的第二笔交易由资产 Safe 执行批次。
图 3:入队与执行相隔 1,480 秒;历史配置的 cooldown 是 180 秒、expiration 是 1,800 秒,可执行区间为创建后第 180 至 1,980 秒。

两笔样本交易相隔 24 分 40 秒,配置中的 cooldown 只有 180 秒。执行发生在创建后第 1,480 秒,已经越过最早执行点,距离 expiration 终点仍有 500 秒。队列创建、时间开放和资产效果由不同状态与收据证明;这三个时间概念在响应计划里分别决定发现、取消和最终对账。

2.1 三份摘要约束不同状态,部署补丁不会追溯重验旧队列

moduleTxHash 是 Delay 域下的 EIP‑712 摘要,绑定含选择器的规范调用与 timestamp-derived salt,并作为 consumed[signer][moduleTxHash] 的键。fallback handler 又把这份摘要包装成 signer Safe 域下的 SafeMessage。Delay 的队列哈希只绑定 to/value/data/operation,数值 nonce 负责定位映射项。调查工具要保留三种身份,避免用一个“交易哈希”覆盖重放、签名和排队三个问题。

这套状态划分解释了修复顺序。新 checker 会拒绝新的失败魔数请求,却不会被 executeNextTx() 再次调用;索引 11 仍依据旧队列哈希与时钟执行。运营记录需要两个闭环区块:新脆弱授权停止产生的区块,以及最后一项由旧谓词产生的授权不再可执行的区块。只有后者到达后,账户才完成这条漏洞路径的技术收口。

3 暴露范围由运行时代码、签名可达性和队列状态共同决定

潜在范围从实际链上实例开始。对每条支持链列出 Safe、singleton、owner、threshold、guard、fallback handler、已启用模块、模块代理、实现、runtime hash、cooldown、expiration 和队列头尾;沿 minimal proxy、实现槽、工厂参数或自定义 master-copy 关系找到真实代码。浏览器名称、ABI、包锁文件和字节码长度只用于发现候选,最终分类绑定事件区块上的执行路径。

运行时分类采用 vulnerable、fixed、unknown 三态。固定源码或可复现构建能够直接定位 _isValidContractSignature();来源未知的实现可在固定区块 fork 中让两个 signer 返回同样四字节,分别以 RETURNREVERT 结束。两者都授权说明该运行时保留回归,只有正常返回通过说明这项缺陷已关闭。真实生产模块调用可能写队列,动态分类应在 fork 或专用低价值账户完成。

可达性随后缩小候选集:确认 public moduleOnly 入口、附加签名尾部、合约 signer、模块启用状态与 caller 能力。队列状态再提高紧迫性。每只账户按“潜在暴露、已尝试、已排队、已执行、已隔离、已恢复”分别记录证据;“已隔离”要求新脆弱授权停止且旧项目全部终结,“已恢复”还要求权限可信和用户价值对平。

3.1 精确资产明细进入一套账,暴露、转出、可支配与恢复分别记账

Gnosis 资产表给出的总额约为 1,496,151 美元:GNO 641,159 美元、EURe 453,175 美元、USDC.e 399,121 美元、SAFE 2,202 美元、WETH 323 美元、xDAI 135 美元、USDC 28 美元、USDT 7 美元。分项以整数美元展示,逐项相加与“约”总额存在 1 美元舍入差。报告保留官方总额与每项数值,原始 token 数量和日志索引作为跨时间不变的底账。

四套账户账本分别记录潜在暴露、实际转出、攻击者可支配部分和用户恢复金额。
图 4:安全事件与恢复需要四套账。潜在暴露、链上转出、攻击者可支配价值和用户恢复金额在同一时刻可以不同。

约 180 万美元总暴露覆盖 5,281 个余额不低于 1 美元的钱包,高于实际转出表;约 30 万美元被项目列为攻击者无法支配。公开复盘没有给每一美元附同一种链上原因,账户账本据实际证据记录冻结、失败执行、取消、未兑现或未知终态。Gnosis 使用自有资金恢复用户余额,因此财务恢复额还可能高于资产追回额。

EURe 迁移、包装和中间合约会在一条调用链里产生多个 Transfer。核算以 token 合约、uint256 原值、decimals、日志索引和交易前后余额建立 flow graph,再计算攻击者控制地址的净流入。美元估值另存价格来源与时间,不覆盖事件当日的官方口径。

代表性样本的 17.855442968223287575 GNO 与 364.071530831603544366 EURe 来自第二笔执行收据和余额核对,只用于证明执行机制。官方资产表承担全事件范围,两份证据各自回答不同问题。影响对象是采用相关 Delay/Roles 模块和签名路径的 Gnosis Pay card Safe 组合;Gnosis Chain 共识和普通 Safe 核心不属于该范围声明。

4 检测与遏制必须在队列时钟打开前完成

Delay 入队是一项安全状态变化。实时管道把事件与完整 input、历史实现和 trace 关联,输出 caller、Delay、signer Safe、fallback handler、第一槽候选、成员检查到达状态、asset Safe、队列索引、可执行与过期时间、operation、MultiSend 子动作、token、收款人和服务端意图。高价值 delegatecall、权限变化、首次 signer 路径或没有登记意图的项目在创建区块进入人工处置。

失败魔数是高特异信号。归档 trace 或固定区块重放查找 ERC‑1271 调用的失败状态与魔数前缀,再确认祖先帧到达 Zodiac checker。顶层 receipt 没有内部调用的 status/data 对,日志管道需要专门保存 trace 或可复现的历史重放材料。

trace condition:
  call.type        = STATICCALL
  call.selector    ∈ {0x1626ba7e, 0x20c13b0b}
  call.success     = false
  call.output[0:4] = 0x1626ba7e

enrichment:
  signer Safe / handler / Zodiac ancestor
  membership reached or not reached
  queue mutation and registered intent

执行阶段按 caller、beneficiary、inner target、runtime hash 和规范化 batch hash 聚类,统计十分钟内涉及的 Safe 与资产种类。托管系统同时保存请求 ID、会话、设备、signer 方法、relayer、Safe、salt、预期摘要和交易哈希。行为 trace 负责发现换地址或重编译的 validator,意图记录负责区分合法直接调用与未授权排队。

4.1 队列洪泛要求角色分离、速率限制和可审计的批量取消

攻击者可以用大量低价值项目占满人工审查能力,并把高影响操作夹在其中。生产设计按 role 或风险等级分离队列,对 caller、账户和时间窗口设置链上或可验证的排队速率限制;owner、module、guard、upgrade、无限 allowance、任意 delegatecall 与高价值 transfer 使用更长冷却。限制只用于降低审查负载,拒绝结果仍要留痕,避免洪泛被当成静默丢包。

批量取消必须匹配实际版本。样本 v1.1.0 只能用 setTxNonce() 跳过一段连续索引,因此操作前要列出该区间每项 hash、用户意图、最大影响与新队头,完成独立授权并在上链后回读。未来实现若提供按 hash 或按 role 批量取消,应让 guardian 只能撤销、暂停、延长冷却或禁用模块,无法转移资产、添加 owner 或降低 threshold。

处置次序服从依赖关系:

  1. 冻结新增授权。停止全部旧签名 API、relayer 和后台任务,保存请求、配置、trace 与队列快照。
  2. 按剩余时间排序。解码全部未决项目,优先处理权限变化、delegatecall、批准和高价值资产动作。
  3. 使用独立权限中和。取消、批量跳过、禁用模块或保护资产,并为每项记录终态。
  4. 替换脆弱运行时。固定源码、构建和链上代码,关闭所有委托到旧 checker 的替代路径。
  5. 恢复账户与价值。从可信区块复核权限和余额,完成财务对账与用户通知。
  6. 成对验收后分批开放。正常 signer 通过,回滚魔数 signer 在任何队列副作用前失败。

SOSEC 起始建议:高价值队列的 cooldown 至少覆盖最近九十天“发现+意图确认+紧急签名+链上 finality”的本地 P99,并加入经压力测试得到的拥堵缓冲。owner 是钱包运营与合约工程共同责任人;测量窗口、阈值和回滚触发按链校准。样本的 180 秒历史配置归入事件条件,生产基线使用上述本地测量值。

5 修复终点是运行时、旧队列、账户权限和用户资产同时可信

升级清单以链上关系为主键:chain ID、Safe、模块代理、implementation、eth_getCode、runtime hash、源提交、编译器、优化器、库、immutable、初始化数据、替换交易和启用/禁用区块。固定源码只说明某个构建应该怎样运行,链上 hash 与升级交易才说明用户账户何时获得该行为。

新部署或可迁移系统优先验证当前 4.3.0;v3.0.2 只表示 v3 谱系的首修边界。兼容性测试覆盖 EOA、预批准 hash、标准 ERC‑1271、嵌套 Safe、Roles、Delay 与现有业务编码。若模块不可升级,部署新实例并复核参数,再以原子或明确排序的 Safe 交易禁用旧模块、启用新模块;旧地址、初始化值和替换交易进入资产目录。

代码替换前后都要处理队列。先枚举旧 runtime 已经认证的项目,固定创建区块、可执行时间、过期时间、内部动作和意图;升级后再次读取,确认取消、跳过、过期或重新授权的结果。只升级 checker 会留下已保存执行权,只清队列会让脆弱入口继续产生新项目,两项需要在同一变更单中闭合。

账户恢复从恶意排队之前的可信区块开始,对比 owner、threshold、guard、fallback handler、modules、allowances、delegate 权限、session 与余额。当前状态可能已经包含排队操作造成的权限变化,直接复制到新模块会保留风险。技术恢复和财务恢复各有时间与证据,二者都完成后才写用户闭环。

响应工作台依次停止新增授权、清理队列、替换运行时代码、复核账户关系并恢复用户资产。
图 5:暂停入口之后,恢复继续贯通队列、实现、权限、余额和用户通知。

5.1 成对 canary、回滚条件和分批恢复构成生产准入

正向 canary 使用标准合约 signer 正常 RETURN 魔数,经生产形状路径生成一项无害队列记录,并在监护下取消或执行。负向 canary 使用 REVERT(0,4) 携带相同魔数,预期在 signer 恢复处失败,consumed、queue nonce、hash、timestamp 与事件保持不变。两个样本都绑定登记意图,防止测试白名单掩盖行为告警。

回滚触发包括 runtime hash 不匹配、链上实现指向错误、合法 signer 兼容性失败、回滚魔数产生任何副作用、旧队列遗漏、账户权限无法对平或监控未观察到关键阶段。回滚操作保持托管入口暂停,恢复已验收的实现与模块关系,并保留已经完成的安全性取消;恢复旧制品前先确认其中包含修复,防止回退重新引入 data-only 谓词。

恢复按 cohort 进行。每批记录 Safe 数量、资产上限、入口、实现 hash、正负验收、队列终态与观察窗口;异常立即停止下一批并回滚当前批。全量开放后继续追踪从首个脆弱部署区块到“最后旧授权失效区块”之间的失败魔数、异常队列和批量执行,命中项进入账户级复核。

用户界面把 Delay 操作标为“已排队”,显示可执行、过期、动作摘要与取消路径;执行完成后再更新最终状态。紧急通知至少经过一个独立于主要会话的渠道。用户恢复凭证链接到具体交易、token 原值和账户状态;全局美元总额保留为概览。

6 技术附录按问题保留最少收据:源码解释判断,字节解释输入,trace 解释路径

本附录承载主叙事不需要反复展开的复现细节。每类证据只回答一个问题:源码说明特定版本如何处理调用结果;ABI 与 Account Kit 说明交易字节如何构造;事件区块 runtime 说明未验证 validator 实际执行什么;trace 说明这些对象在样本中以何种顺序和状态相遇;存储与日志说明授权怎样落地并产生资产效果。

6.1 源码收据固定回归、代表部署、修复和当前稳定线

提交 9a9e380 的父版本保留 success && magic,该提交把元组第一项改为空位,并随旧包 3.4.0 发布。3.4.1 快照仍在 SignatureChecker.sol 第 127—135 行使用 data-only 谓词。修复提交 4cf1530 恢复状态项,v3.0.2 对应提交 4a24501572db953f355634e20701dbf50732f625;当前 v4.3.0 对应 b5fad243…,同一安全不变量仍在源码中。

代表性 Delay 代理 0x7ECf…78f3 指向实现 0x4A97E65188A950Dd4b0f21F9b5434dAeE0BBF9f5,其已验证源码与 Delay v1.1.0 提交 515737f67bb704d0ce14d45cd6f7cc8e6b4655d1 一致;package.json 第 1—32 行锁定旧包 3.4.1。execTransactionFromModule() 第 128—140 行写队列,executeNextTx() 第 176—200 行消费队头,第 214—220 行给出四字段哈希公式。

Safe 与 handler 是样本的传播路径。固定提交的 CompatibilityFallbackHandler.sol 第 28—80 行转换选择器并构造 SafeMessage;Safe.sol 第 274—331 行说明候选调用先于成员检查。Safe 后续提交 77901a5a1ad835b74ad3b72f73a8412cfe491c57 又加入成功状态和固定长度检查,属于独立纵深加固;Zodiac 的 success && magic 仍是本案根因修复。

6.2 字节收据固定两个偏移原点、四字节回滚块和链上复算入口

代表性交易 input 长 871 字节。Zodiac 从完整 msg.data 尾部读取外层 r=0xce7d…5561、绝对 s=0x244v=0,将 0x244 到 salt 之间的 0xc2 字节交给 signer Safe。Safe 在这段切片中建立新原点,第一槽为候选 0x5a779…97a35、相对 s=0x82v=0;第二槽是攻击者 EOA,但 threshold 1 的流程在第一槽回滚时已经结束。

complete calldata origin:
  outer r = signer Safe 0xce7d…5561
  outer s = 0x244 → 0xc2-byte Safe signature slice
  salt    = ABI-encoded millisecond timestamp

Safe-slice origin:
  slot 0 r = candidate 0x5a779…97a35
  slot 0 s = 0x82 → length 0x20 || 32 zero bytes
  slot 1   = present, unprocessed at threshold 1

固定区块读取显示 signer Safe 的 threshold 为 1,owners 是 0x25d21d6a390b41ae032121761f3f62b8f675713e0xe92ed76e5b853f246fd6dc8bef58339ed1fa05bd0xc75e0137aeb875b76f326481bdbf19877ce01c4d0x1f778edc79aec4e17ea4daf8e2f438cec26a7d13。候选地址不在集合中,trace 在 GS026 前结束。该事实在机制段用于校正角色,在最终案件边界中说明本文没有把 calldata 候选写成已取得 owner 权限。

恶意合约没有已验证源码。事件区块 runtime 暴露 0x1626ba7e 与 legacy 0x20c13b0b 两个 ERC‑1271 选择器,终止基本块通过 PUSH4 0x0b135d3f → SHL 0xe1 → MSTORE → REVERT(0,4) 形成 0x1626ba7e。固定区块 eth_call 与攻击 trace 都观察到失败状态和同一错误 data;这些机器行为足以分类 validator,作者、源语言与侦察过程仍没有公开证据。

独立复算顺序是:保存 raw input、block hash、实现槽与 runtime;按四参数 Delay ABI 划定规范调用,再按 Zodiac 尾部语法和 Safe 相对偏移解析签名;用实际公式计算 ModuleTx 和 queue hash;关联索引 11 的创建状态与执行 input;检查第二笔 receipt 的 token 日志与余额;最后在固定区块 fork 中比较旧 checker、固定 checker 和合法 signer。逐字节 raw input、完整 runtime 与 RPC JSON 属于受控案件材料,公开文章保留选择器、偏移、地址、行段与交易哈希以支持复核。

公开证据足以确定漏洞谓词、代表性部署、嵌套签名、队列/执行配对、资产效果与项目恢复。公开复盘没有逐账户列出所有模块替换、取消和权限恢复交易,也没有完整披露攻击者筛选账户的方法。运营方需要用自己的历史账户图、请求日志和链上回执补齐这些对象;缺少其中一项时,结论记录为 unknown 或 pending,不由事件成功反推。

7 收尾:四字节只描述数据,授权必须保留调用状态

本案根因是一条可验证的布尔表达式:受影响 Zodiac checker 接收 returnData 的魔数,却丢失同一次调用的 success=false。代表性路径随后把误判写入 Delay 索引 11,并由另一只资产 Safe 在可执行窗口内完成 MultiSend。修复也有明确边界:部署 success && magic 的可验证运行时,关闭旧模块,处置旧队列,恢复账户状态和用户价值。

可复用的工程规则很简单:任何低级调用只要为授权、价格、所有权或执行提供证据,status 与 data 就是一项不可拆分的不变量;任何延时系统只要持久化授权,监控、独立取消权和足够的响应时间就共同构成它的安全价值。正负 canary、runtime 清单和队列终态把这条规则从代码意见变成可验收的生产控制。

Gnosis 已恢复用户余额,为这次事件提供了财务终点。每个其他部署仍以自己的链上运行时、旧队列与账户权限为准。读者关闭本文时应能准确复述四件事:连接哪四个对象,哪一个谓词出了错,为什么补丁不会自动清掉旧执行权,以及账户满足哪些证据后才能恢复服务。

研究记录

8证据、对象与来源

下面保留本文实际使用的标识、时间和原始材料,便于继续调查。

8.1研究对象

报告涉及的产品、行为者、技术、受影响对象和控制点。

受影响组件Delay v1.1.0 / @gnosis.pm/zodiac 3.4.1;受影响 checker 谱系始于 3.4.0

代表性 Delay 继承的 ERC-1271 checker 丢弃低级调用状态

当前评估目标@gnosis-guild/zodiac-core 4.3.0

截至 2026 年 7 月 27 日的 npm 当前稳定版;生产仍需本地兼容与 runtime 验收

攻击合约0x5A77953CAa27eD4638F4DfdC665b8064D0e97A35

回滚 ERC-1271 魔数的 Gnosis Chain 合约

攻击者 EOA0x81BA8A2b895D30280bca199C2Ff75f3F058d4C6c

部署恶意合约并调用多个 Delay

魔数0x1626ba7e

ERC-1271 isValidSignature(bytes32,bytes) 成功返回值

修复提交4cf15302228a578985b0af2a3e7000dcded2da6b

恢复 staticcall success 条件

回归提交9a9e38025ad7518f6e5350aeac673435a7db70f1

旧包 3.4.0 引入忽略调用状态的变化

样本排队交易0x5f562057e1963964fc3a6d8b105057f3ab9f9dd7091ff5bdd8d51453f51bd456

08:29:05 UTC 创建 Delay 队列项 11

样本执行交易0x4d3888cf3c68bffb2013001aabaf7b3596fc5d3f5d4cc5f139487d8a4140ae14

08:53:45 UTC 执行同一 MultiSend 批次

8.2事件时间

  1. 旧包 Zodiac 3.4.0 发布

    签名重构进入发布版,低级调用成功位被忽略。

  2. 恶意 ERC-1271 合约部署

    攻击者 EOA 在 Gnosis Chain 创建回滚魔数合约。

  3. 首笔大额资产转移

    Gnosis 监控观察到首笔大额未授权转账。

  4. 根因确认

    团队定位 Zodiac ERC-1271 结果处理并暂停相关服务。

  5. 修复与回归测试提交

    成功位检查恢复,回滚魔数测试加入代码库。

  6. 公开复盘当前版本

    Gnosis 页面记录当前影响、恢复与根因说明。

  7. 当前稳定线复核

    npm 当前稳定版为 @gnosis-guild/zodiac-core 4.3.0。

8.3来源与材料

  1. Gnosis 官方事故复盘https://www.gnosis.io/blog/post-mortem-gnosis-pay-vulnerability-exploit
  2. EIP-1271:合约签名验证标准https://eips.ethereum.org/EIPS/eip-1271
  3. Zodiac Core 回归提交 9a9e380https://github.com/gnosisguild/zodiac-core/commit/9a9e38025ad7518f6e5350aeac673435a7db70f1
  4. Zodiac Core 修复提交 4cf1530https://github.com/gnosisguild/zodiac-core/commit/4cf15302228a578985b0af2a3e7000dcded2da6b
  5. 回滚魔数回归测试 b87fe79https://github.com/gnosisguild/zodiac-core/commit/b87fe79e66c9c5bc3c46f3018ea53ebddbed516b
  6. 代表性 Zodiac Delay v1.1.0 固定源码https://github.com/gnosisguild/zodiac-modifier-delay/blob/515737f67bb704d0ce14d45cd6f7cc8e6b4655d1/contracts/Delay.sol#L108-L220
  7. @gnosis-guild/zodiac-core 4.3.0 当前稳定包https://www.npmjs.com/package/%40gnosis-guild%2Fzodiac-core/v/4.3.0
  8. Safe 1.4.1 核心源码https://github.com/safe-fndn/safe-smart-account/blob/bf943f80fec5ac647159d26161446ac5d716a294/contracts/Safe.sol
  9. Safe 1.4.1 签名解码源码https://github.com/safe-fndn/safe-smart-account/blob/bf943f80fec5ac647159d26161446ac5d716a294/contracts/common/SignatureDecoder.sol
  10. 历史 Gnosis Pay Account Kit 交易与 ERC-1271 编码https://github.com/gnosispay/account-kit/blob/51f60789df3252c06aab963654a183f25abc6afd/src/entrypoints/accounts-actions/execute.ts#L43-L318
  11. Safe 1.4.1 compatibility fallback handler 路径https://github.com/safe-fndn/safe-smart-account/blob/bf943f80fec5ac647159d26161446ac5d716a294/contracts/handler/CompatibilityFallbackHandler.sol#L28-L80
  12. Safe4337Module v0.2.0 handler 继承https://github.com/safe-global/safe-modules/blob/d5de32548c968533ace53f514fd8ce2d276c1a27/4337/contracts/Safe4337Module.sol#L1-L22
  13. Safe4337Module v0.2.0 部署清单https://github.com/safe-global/safe-modules-deployments/blob/e6b1e025b281d295224408ea8be54a89ea3dd060/src/assets/safe-4337-module/v0.2.0/safe-4337-module.json
  14. Safe 事后纵深加固提交 77901a5https://github.com/safe-global/safe-smart-account/commit/77901a5a1ad835b74ad3b72f73a8412cfe491c57
  15. Safe 非 owner 回滚合约回归测试https://github.com/safe-global/safe-smart-account/blob/77901a5a1ad835b74ad3b72f73a8412cfe491c57/test/core/Safe.Signatures.spec.ts#L1123-L1145
  16. 恶意 ERC-1271 合约与字节码https://gnosis.blockscout.com/address/0x5A77953CAa27eD4638F4DfdC665b8064D0e97A35
  17. 恶意合约创建交易https://gnosis.blockscout.com/tx/0x771a8040f3ae89fd28eecb96bbe1bfe452a868a09febe0368eb00a1105603ea2
  18. 代表性排队交易https://gnosis.blockscout.com/tx/0x5f562057e1963964fc3a6d8b105057f3ab9f9dd7091ff5bdd8d51453f51bd456
  19. 代表性执行交易https://gnosis.blockscout.com/tx/0x4d3888cf3c68bffb2013001aabaf7b3596fc5d3f5d4cc5f139487d8a4140ae14
  20. 代表性 signer/module Safehttps://gnosis.blockscout.com/address/0xce7d2f8e54e1104ed7a4c1f88dac63aa4a825561
  21. 代表性资产/card Safehttps://gnosis.blockscout.com/address/0x0cae88383f2d1f308292e58cd71984bb6120e03d
  22. Safe 智能账户签名说明https://docs.safe.global/advanced/smart-account-signatures
  23. Solidity REVERT 与错误数据语义https://docs.soliditylang.org/en/latest/control-structures.html#revert-statement