区块链桥接安全 - 介绍与第一部分

本文是关于区块链桥接安全漏洞系列文章的开篇,主要介绍了跨链桥的基本概念,并通过一个示例项目概述了消息重放和签名重放两种常见的桥接漏洞,以及如何利用这些漏洞。

介绍:

区块链桥梁是 web3 生态系统中的关键基础设施,支持不同区块链网络之间的跨链通信和资产转移。本系列将通过示例解释一些常见的/可能的桥梁智能合约漏洞。

使用的示例来自我过去的示例项目仓库 Blockchain-bridge-vulnerabilities

在继续之前,了解一下 EIP712 会很有帮助,可以在这里找到。

本系列解释了以下漏洞:

  1. 消息重放 (Message replay)
  2. 签名重放 (Signature replay)
  3. 跨链签名重放 (Cross chain signature replay)
  4. 签名重放的变种 (Variations of signature replay)
  5. 任意调用执行 (Arbitrary call execution)
  6. 攻击者窃取以太币 - test_sendMsg_nested()
  7. 攻击者欺骗铸币逻辑 - test_sendMsg_nested_transfer()
  8. 链 ID 欺骗 (Chain id spoofing)
  9. 哈希碰撞 (Hash collision)
  10. 缺少签名过期检查 (Lack of signature expiry check)

本文概述了示例项目中的合约以及提到的 2 个漏洞:

  1. 概述
  2. BridgeSafeTokenSend.sol
  3. SignalProcessor.sol
  4. 漏洞:
  5. 消息重放 (Message replay)
  6. 签名重放 (Signature replay)

概述:

本示例项目中使用的例子是一个主要允许两件事的桥:

  1. 允许在源链上销毁代币,并在目标链上铸造相同数量的代币。
  2. 允许将目标链上拥有的代币转账到目标链本身上的新地址。

示例桥协议的高级表示

BridgeSafeTokenSend.sol 的概述 :

顾名思义,它显示了假定免受本示例中提到的其他攻击的功能。(仍然可能存在漏洞。)

  1. sendMsg():

通过调用 SignalProcessor. sendTx()SignalProcessor 中记录消息。此消息将由 relayer 发送到目标链(将在下面详细介绍)。一般来说,它有助于从源链发送消息。此外,还需要一些 staticFee(在现实世界中可能是动态的)

  1. sendMsgPermit():

sendMsg() 的工作方式相同,但使用 ECDSA 验证来验证代表实际签名者发送的消息。

显示了 sendMsg()sendMsgPermit() 的预期逻辑

  1. executeMessage():

通过首先在目标链的 SignalProcessor 上验证消息来执行目标链上的消息。

  1. 然后是一些辅助函数,例如:
  2. getDomainSeparator():

    返回域分隔符值。

  3. pauseBridge():

    暂停桥。

  4. unpauseBridge():

    取消暂停桥。

所有函数都带有许多重要的检查/验证。

SignalProcessor.sol 的概述:

SignalProcessor 是用于存储和验证消息是否有效的合约。存储的消息由 relayer 跨链共享。为了简单起见,此合约充当模拟信号处理器。

  1. sendTx():

存储桥合约共享的消息哈希。假设有足够的检查来检查它是否仅来自桥合约。

  1. verifyTx():

验证消息是否从源链发送,并返回 true/false 布尔值。此模拟合约不包含证明验证逻辑。但是我们可以假设存在证明验证机制。

重要的是要注意,对于这些示例,只会部署一个 SignalProcessor 实例,并且源链和目标链上的桥都将与同一 SignalProcessor.sol 实例的地址交互。在现实生活中,将在源链和目标链上部署两个 SignalProcessor 合约,并且 relayer 将传输和存储从源链发送到目标链的消息。

除了上面提到的这两个合约之外。对于本系列中解释的每个漏洞,都有不同的合约和/或各自的测试用于展示该漏洞。BridgeSafeTokenSend.sol 主要显示了没有在整个项目/文章中解释的其他漏洞的合约代码。


漏洞:

1. 消息重放:

test_executeMessage_replay() 显示了如何重用消息。正如在 executeMessage() 函数中看到的那样,它不检查消息是否已过期(你可以在 BridgeSignatureReplay.sol#L114-L116 上看到它已被注释掉)。

该测试显示了相同的事情,它首先创建 transaction 结构,然后使用 bridge.sendMsg() 发送消息,该事务是在另一条链上铸造 1e18 个代币,方法是在当前链上销毁 1e18 个代币。

然后它调用 destBridge.executeMessage() 两次,第一次在 BridgeSignatureReplay.t.sol#L126 上,第二次在 BridgeSignatureReplay.t.sol#L129 上,这表明由于没有存储和验证消息哈希,因此可以使用消息多次。这很简单。

2. 签名重放:

test_sendMsgPermit_signature_replay() 显示了恶意用户如何重放签名(在同一链和同一桥上)。在 BridgeSignatureReplay.t.sol#L160 上,它发送带有交易和签名参数 (v, r, s) 的消息。此处的交易是将 50e18 个代币转账到特定地址(在本例中为 user 地址)。正如在 BridgeSignatureReplay. sendMsgPermit()BridgeSignatureReplay.sol#L82 可以看到 transaction.id 的赋值被注释掉了,只是假设它不存在。

但是为什么需要这个呢?

每个交易消息都有 id 变量的原因是为了区分不同的交易。因此,通常当交易由所有者离线签名时,它将包含此唯一的 id

正如可以看到的,transactionHash 是使用 transaction 结构变量的 keccak256 哈希创建的。然后将其用于 ethSignedMessageHash 摘要的创建,然后将其传递给 ecrecover 以恢复签名者的地址。

该函数应该分配 transaction.id = messageId++,这将为每次执行 BridgeSignatureReplay. sendMsgPermit() 时创建不同的 ethSignedMessageHash 摘要。万一任何交易使用相同的 id(为了恶意使用旧签名),该函数将恢复,因为 ecrecover 恢复的地址将不等于 BridgeSignatureReplay.sol#L89 上的 transaction.from,这是因为合约中计算的 ethSignedMessageHash 将不同于创建旧签名(正在重放)的 ethSignedMessageHash

BridgeSignatureReplay.t.sol#L160 上发送消息后,它在 BridgeSignatureReplay.t.sol#L165 上调用 destBridge.executeMessage(),之后在 BridgeSignatureReplay.t.sol#L167-L168 上,它检查 Alice 是否将 50 个代币转账到 user 地址。

此后,在 BridgeSignatureReplay.t.sol#L173 上,它再次调用 destBridge.executeMessage() 以执行签名重放攻击。在当前示例中,由于赋值被注释掉了,因此用户可以使用以前的签名(带有旧的 id),这类似于重用任何先前签名的交易,即重放它。

最后,在 BridgeSignatureReplay.t.sol#L175-L176 上,可以看到 user 现在有 100 个代币,即使 Alice 只想转移 50 个代币。

此实现中的 id(当前已注释掉)的作用类似于 nonce,它通过强制签名者为每个要签名的新 transaction 结构使用新的 Id 来防止签名重放攻击。

本文到此结束。下一部分(第 2 部分 )显示:

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

⚠️

所有使用的代码/示例都未经过审计且存在漏洞。请勿在生产环境中使用。

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

0 条评论

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