区块链桥安全 - 第二部分

本文是桥安全系列的第二篇文章,主要讲解了跨链签名重放漏洞及其变种。文章通过实例分析了由于缺少对srcChainId的验证,导致签名可以在不同的链上重放,从而使攻击者能够获取额外的代币。此外,文章还讨论了当合约升级或更新时,如果域名分隔符(如名称、版本、验证合约地址)发生变化,可能导致签名重放漏洞的出现。

这是 Bridge 安全系列的第二篇文章。参见简介和第 1 部分

本文解释了以下提到的 2 个漏洞:

  1. 跨链签名重放
  2. 签名重放的变体

1. 跨链签名重放

test_sendMsgPermit_cross_chain_signature_replay() 展示了跨链签名重放攻击是如何工作的。

跨链签名重放的可视化表示。

在此之前,让我们了解一下 domain separator 的用途:

它用于区分在此合约上和任何其他合约上签署的签名,甚至区分在不同链上签署的签名。通常它包含这些参数:

  1. name:合约的名称。
  2. version:合约的版本。可能会发生同一个项目再次部署其合约的情况,因此版本可能高于旧版本。
  3. chainId:用于区分签名所针对的链的 chainId。
  4. verifyingContract:通常是此合约的地址(address(this))。

让我们来谈谈这个示例测试。

在阅读时请参考图表,以便更直观地理解。

test_sendMsgPermit_cross_chain_signature_replay() 中,它简单地创建了一条交易/消息,用于将 50e18 个 token 转移到目标链上的 user 地址。然后它对消息进行签名,并通过 sendMsgPermit() 发送消息。

然后,在链 id 2 上创建 bridge 合约的新实例,称为 destBridgeOnChainId2。它还向 Alice 铸造了 100 个 token。(铸造这 100 个 token 的原因是目标链上需要有一些金额,因为此处的交易是为了将 50 个 token 从 Alice 在目标链上拥有的 token 转移到目标链上的 user)。

它检查余额,然后使用 executeMessage()BridgeSignatureReplay.t.sol#L214 上执行交易。在检查余额后,可以看到两者现在都拥有 50 个 token。这是正确且预期的。因为 Alice 签署了交易以转移 50 个 token。

现在,在 BridgeSignatureReplay.t.sol#L222 上,恶意用户/攻击者再次使用相同的签名组件和相同的交易结构调用 sendMsgPermit()

然后在目标链 bridge 上使用 destBridgeOnChainId2.executeMessage() 执行相同的交易。

有趣的是,你可以看到最后一个余额断言正在通过,这意味着攻击者能够成功执行跨链重放攻击,并能够从 Alice 的余额中获得所有 token(总共 100 个)。

这种攻击之所以成为可能,是因为 BridgeSignatureReplay.sendMsgPermit() 中注释掉的部分未能分配 transaction.srcChainId。因此,即使签名者签署了具有特定 srcChainIdtransaction 结构(假设为 31337,或图表中的链 1),仍然可以在任何其他链上使用该签名,而不是 31337(或是最初创建和签名结构的链)。

因此,在合约级别发生的事情是,在 BridgeSignatureReplay.sendMsgPermit() 中,在创建 ethSignedMessageHash 时,使用了与 id 为 31337 的链上相同的 transactionHash,并且由于攻击者使用了原始交易结构和正确的签名,因此恢复的签名与 transaction.from 匹配。

如果 BridgeSignatureReplay.sol#L81 的赋值发生了(目前没有发生,因为它被注释掉了),则交易将在 BridgeSignatureReplay.sol#L89 上回滚,因为合约内的 transaction 结构现在将具有不同的 chain id(不是 31337,假设攻击者在不同的链上调用 BridgeSignatureReplay.sendMsgPermit(),而不是 31337,即创建和签署 transaction 结构的链),因此 transactionHash 将会改变,因此 ethSignedMessageHash 也会改变,最终攻击者使用的签名将无效(因为最初它是为具有 srcChainId31337 的不同结构创建的)。

因此,在 test_sendMsgPermit_cross_chain_signature_replay() 的当前示例中,攻击者可以在多个链上多次使用该签名。不仅仅是在签名者签署消息的链上。这就是攻击者能够在目标链上获得更多 token(100e18),而不是签名者想要转移的 token(50e18)的原因。

为了避免此项目中的此漏洞,可以添加 srcChainId赋值,以便创建不同的 transactionHash,这将导致跨链重放交易回滚,如上所述。

这种攻击可能存在变体,它不仅与缺少赋值或未使用的链 id 比较有关,而且仅仅与缺少应该存在的东西有关!更多信息如下。

注意:在此测试的 BridgeSignatureReplay.t.sol#L219-L222 中,注释说“在链 id 3 上的签名重放”,但它仍然使用用于在 BridgeSignatureReplay.t.sol#L201 上在链 1 上发送消息的 bridge 实例。实际上,那将是部署在该链 3 上的 bridge,正如注释所说。

2. 签名重放的变体

其他参数的缺失可能导致此类漏洞,特别是包含在 domain separator 中的参数,如 name、version、verifying contract。

假设项目决定升级(或说更新,它会弃用旧合约)一份合约,其中 name、version、verifying contract address(address(this))等内容可能会发生变化。

例如,如果有一个名为 XYZProject 的项目,它使用带有 EIP712 的 ECDSA。最初的 domain separator 值为

name : "XYZProject"

version : 1

chainId : 111 或是部署合约的链。

verifyingContract : address(0x1XyZprOject) 或任何地址。

为此合约签署的所有签名都将在其 domain separator 中使用这些链下值,然后将其用于创建将要签名的摘要。

在合约升级或说新合约部署之后,这些值可能会发生变化。假设新值为:

name : "NewXYZProject"

version : 2

chainId : 111 或部署合约的任何其他链。

verifyingContract : address(0x1NeWXyZprOject) 或任何其他地址。

因此,现在对于合约内验证,创建的摘要(ethSignedMessageHash)将完全不同(因为 domain separator 值不同),因此为先前合约版本创建的签名将无用,因为先前的签名过去常常使用旧的/初始的 domain separator 值。

因此,签名重放可能存在变体,具体取决于合约是否已更新,以及是否仍使用相同的 domain separator 参数,或者根本不使用这些参数。例如,任何具有签名验证逻辑的合约,如果在更新合约或部署新合约后不注意这些值,都可能容易受到签名重放的攻击,因为先前签署的签名可以与新合约一起使用。

下一部分(第 3 部分)展示了任意调用执行

  • 原文链接: calibersec.com/blockchai...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
calibersec
calibersec
江湖只有他的大名,没有他的介绍。