| 时间 | 2024 年 7 月 18 日 |
| 涉案 | 约 $2.35 亿 |
| 多签配置 | 正常工作 |
| 硬件钱包 | 正常工作 |
| 类别 | C7 密钥与签名流程 |
一、背景:多签到底在防什么
一个 3/6 的多签钱包,它防的是:
✅ 一个人卷款跑路
✅ 一把私钥泄露
✅ 一个人被胁迫
它的安全性建立在一条假设上:
⭐⭐ 【每一个签名者,都独立地理解并同意了他所签署的那笔交易。】
如果这条假设不成立——如果六个人看到的都是同一个被篡改过的界面——那么 3/6 就退化成了 1/1:
⚠️ 攻击者只需要骗过【那一个界面】,
六个签名者会依次为他按下确认。
⭐ 多签的"多",防的是【意图不一致】,
而不是【信息不一致】。
二、漏洞在哪:签名者看到的 ≠ 签名者签的
现代多签(Safe 是最常见的一种)的流程是:
① 发起方在【界面上】构造一笔交易
② 界面把它序列化成一段 calldata
③ ⭐ 计算这段 calldata 的哈希
④ 签名者依次用硬件钱包对【那个哈希】签名
⑤ 攒够门槛 ⟹ 提交上链执行
⚠️ 第 ③ 到 ④ 步之间有一道鸿沟:
签名者的硬件钱包屏幕上显示的是什么?
⭐ 理想情况:完整解析出来的交易内容
"向 0xABC 转账 100 ETH"
⚠️ 实际情况(尤其是复杂交易):
一串 32 字节的十六进制哈希
"0x7f3a9c...e21b"
⟹ ⭐⭐ 签名者无法从这个哈希反推出交易内容。
他只能相信【屏幕上另外那个界面】告诉他的说明。
⭐⭐ 这就是"盲签"(blind signing): 你签的是一个哈希,而你对它含义的全部理解,来自一个可能已经被攻破的界面。
在 WazirX 的事故中,签名者看到的交易内容与实际被签署的载荷不一致。 签名者逐个确认了一笔他们以为无害的操作,而实际签署的是一笔把钱包控制权/资产转走的交易。
三、⭐ 这个攻击为什么如此有效
攻击者需要攻破的东西:
❌ 不需要私钥 —— 签名者自己会签
❌ 不需要绕过多签门槛 —— 门槛会被正常满足
❌ 不需要合约漏洞 —— 合约会正确执行
⭐ 只需要控制【签名者获取信息的那个渠道】
⟹ 而那个渠道通常是:
· 一个网页前端
· 一个 API 返回的交易描述
· 一个内部工具的界面
⚠️ 全都在传统 IT 安全的范畴内,
而不在"区块链安全审计"的范畴内。
⭐ 注意责任的错位:
智能合约审计公司:审的是链上代码 ✅ 没问题
渗透测试公司: 审的是 IT 基础设施
⚠️ 但他们不理解"签一个哈希"意味着什么
⟹ 这个攻击落在两者之间的缝里。
四、防御
① 独立校验 calldata
⭐⭐ 这是唯一真正有效的防御:不要相信任何界面,自己解析。
签名前,在【一台独立的设备】上:
① 从多签合约直接读出待签交易的原始参数
(to / value / data / operation / nonce)
② ⭐ 用独立的工具解析 data ——
解析出的函数名和参数是什么?
③ 自己按 EIP-712 重新计算一遍交易哈希
④ ⭐⭐ 与硬件钱包屏幕上显示的哈希【逐字符比对】
⑤ 只有完全一致,才按确认
关键在"独立"两个字:这台设备不能是发起交易的那台,用的工具不能是同一个前端提供的。
② 特别警惕 operation = 1
Safe 的交易有一个 operation 字段:
operation = 0 ⟹ CALL (普通调用)
operation = 1 ⟹ ⭐⭐ DELEGATECALL
⚠️ DELEGATECALL 意味着:
在【钱包自己的存储上下文】里执行别人的代码
⟹ 可以改写钱包的 owner 列表
⟹ 可以改写钱包的实现逻辑
⟹ ⭐ 等于把整个钱包交出去
⟹ 而在界面上,operation 通常是一个不起眼的小字段,
甚至根本不显示。
⭐ 规则:任何 operation = 1 的交易,都必须触发最高级别的审查流程。 日常的资金转账永远不需要 delegatecall。
③ 签名者必须独立发起校验,而不是被动接收
⚠️ 常见的错误流程:
运营人员在群里发一张截图 + 一个链接
⟹ 签名者点开链接,看到界面,按确认
⭐ 正确的流程:
签名者【自己】从链上读取待签交易,
自己解析,自己算哈希。
⟹ 发起方提供的任何信息都只是【参考】,不是【依据】。
④ 带外确认
⭐ 对超过阈值的交易:
□ 通过【另一个渠道】(电话/面对面)确认交易的意图
□ 由【不同的人】独立复核 calldata
□ ⚠️ 确认时要念出【解析后的参数】,而不是"那笔转账"
⑤ 结构性防御:让大额转账不依赖签名者的判断
⭐ 与其指望人每次都仔细核对,不如让危险操作在结构上变难:
□ 冷钱包只允许转到【预设的白名单地址】
⟹ 白名单本身的变更走更长的时间锁
□ ⭐ 单笔/单日转账限额,超限需要额外流程
□ 大额转出走时间锁,给监控留出反应窗口
□ ⚠️ 禁止 delegatecall —— 用一个模块守卫强制拦截
⭐⭐ 这是本篇最重要的一条:人的注意力是不可靠的资源,而"限制能做什么"比"要求人看仔细"可靠得多。
五、⭐ 举一反三
核心命题一:签名的语义鸿沟
⭐⭐ 签名只能证明"某把私钥同意了某个哈希"。 它不能证明"那个人理解了那个哈希的含义"。
⚠️ 同一个鸿沟的其它形态:
· ⭐ 无限 approve —— 用户以为"授权这一次交易",
实际授权了"这个合约可以随时转走我全部代币"
· Permit 签名 —— 一个看起来像"登录"的签名,
实际是一份转账授权
· EIP-712 结构化数据 —— ⚠️ 如果钱包不解析,
用户看到的仍然是一串哈希
· 链下订单签名 —— 签的是"挂单",
而参数组合可能允许对手方以任意价格成交
· ⚠️ 跨链消息签名 —— 在 A 链签的,可能在 B 链被使用
核心命题二:多签的真实门槛(续第 22 篇)
[第 22 篇](https://blog.ifcalm.org/posts/security/blockchain/22-ronin-bridge/):密钥【物理上】不独立 ⟹ 门槛虚高
⭐ 本篇: 签名者的【信息来源】不独立 ⟹ 门槛虚高
⟹ 完整的多签独立性检查:
□ 密钥存放在不同的地方吗?
□ 密钥持有人属于不同的组织吗?
□ ⭐ 他们各自【独立地】校验交易内容吗?
□ ⭐ 他们用的是【同一个前端】吗?
□ 他们的设备来自同一批采购吗?
□ ⚠️ 他们会不会因为"别人已经签了"而放松审查?
⟹ 最后一条是社会性的,但同样致命 ——
⭐ 第 3 个签名者的审慎程度,通常远低于第 1 个。
核心命题三:从"审计代码"到"审计流程"
⭐ 一个完整的安全评估必须覆盖:
代码 —— 合约审计(大多数团队只做这一项)
参数 —— 部署配置([第 7 篇](https://blog.ifcalm.org/posts/security/blockchain/07-nomad-bridge/) Nomad)
工具链 —— 编译与构建([第 20 篇](https://blog.ifcalm.org/posts/security/blockchain/20-curve-vyper/) Curve)
⭐ 流程 —— 谁在什么情况下按什么键(本篇)
⭐ 人 —— 培训、演练、社会工程([第 22 篇](https://blog.ifcalm.org/posts/security/blockchain/22-ronin-bridge/) Ronin)
⚠️ 后两项几乎从不出现在任何审计报告里,
⭐ 而它们是近年损失最大的来源。
一条可以立刻执行的检查
⭐ 问你的多签签名者一个问题:
"上一次你签名之前,
你是怎么确认那笔交易的内容的?"
⚠️ 如果答案是"运营发的截图"或"我看了界面",
⭐ 你的多签实际上是 1/1。
六、本案小结
- 多签正常工作、硬件钱包正常工作、签名者本人确实按了确认——问题是他们在屏幕上看到的内容,和实际签署的字节不是同一个东西。
- ⭐⭐ 多签的安全性建立在"每个签名者独立地理解并同意了他签的东西"上。 六个人看到同一个被篡改的界面时,
3/6退化成1/1——多签的"多"防的是意图不一致,不是信息不一致。 -⭐ 盲签:签名者的硬件钱包屏幕上显示的往往只是一串 32 字节哈希,他无法从哈希反推交易内容,只能相信另一个可能已被攻破的界面。 - 攻击者不需要私钥、不需要绕过门槛、不需要合约漏洞——只需要控制签名者获取信息的那个渠道,而那个渠道(网页前端、API、内部工具)属于传统 IT 安全范畴,不在区块链审计范畴内。这个攻击落在两者之间的缝里。
-⭐ 唯一真正有效的防御是独立校验 calldata:在一台独立设备上,从链上直接读原始参数、用独立工具解析、自己按 EIP-712 重算哈希、与硬件钱包屏幕逐字符比对。
-⭐ 特别警惕
operation = 1(DELEGATECALL):它意味着在钱包自己的存储上下文里执行别人的代码——可以改写 owner 列表、改写实现逻辑,等于把整个钱包交出去。而它在界面上通常是一个不起眼的、甚至不显示的小字段。日常转账永远不需要 delegatecall。 -⭐ “限制能做什么"比"要求人看仔细"可靠得多:白名单地址、转账限额、大额时间锁、用模块守卫强制禁止 delegatecall。人的注意力是不可靠的资源。 - ⚠️ 完整的多签独立性检查还要问:他们用的是同一个前端吗?会不会因为"别人已经签了"而放松审查?——第 3 个签名者的审慎程度通常远低于第 1 个。
- 一条可以立刻执行的检查:问你的签名者"上次签名前你是怎么确认交易内容的”。答案是"看了截图/界面",你的多签实际上是 1/1。
思考题
- 为什么说"多签的多防的是意图不一致,而不是信息不一致"?请用本案说明。
- 硬件钱包显示一串哈希时,签名者能做什么?请设计一个不依赖任何单一界面的校验流程。
operation = 0和operation = 1的区别是什么?请具体说明后者能造成什么后果。- 为什么"日常转账永远不需要 delegatecall"?请给出一个用模块守卫强制拦截它的方案。
- 无限
approve和本案的盲签是同一类问题吗?请说明它们共享的那条语义鸿沟。 - “第 3 个签名者的审慎程度通常远低于第 1 个。“请设计一个流程对抗这种社会性衰减。
- 白名单 + 限额 + 时间锁三条结构性防御,各自能挡住本案的哪一部分?哪一条最有效?
- 一次完整的安全评估应覆盖代码、参数、工具链、流程、人五层。请对你的项目逐层打分。
- 去问你团队的多签签名者本篇最后那个问题。把答案写下来。
- 对比本篇和第 22 篇:一个是密钥不独立,一个是信息不独立。哪一种更难防?为什么?