通过转发功能使Transfer v2通道无法升级的漏洞分析

本文详细分析了Cosmos IBC协议中Transfer v2通道的一个安全漏洞,恶意通道可通过转发功能创建未确认的数据包承诺,阻止合法通道完成升级过程,影响系统可维护性。

通过转发功能使Transfer v2通道无法升级的漏洞分析

影响摘要

一方面,升级功能具有特定的HasInflightPackets检查。它会查找通道的已提交数据包。如果存在任何数据包,就不会将通道设置为FLUSHCOMPLETE状态。这在handleFlushStateWriteUpgradeAckChannelWriteUpgradeConfirmChannel中都有体现。如果通道不处于FLUSHCOMPLETE状态,升级就无法完全完成。在ChanUpgradeOpen中会出错,阻止进入WriteUpgradeOpenChannel(该函数会将通道设置回开放状态并更新升级字段)。

数据包承诺可以通过两种方式删除:确认和超时。

另一方面,转发功能在收到转发目标通道的确认之前,不会为转发的数据包写入确认。

这种转发方法引入了一种情况:合法通道可能变得依赖于恶意通道。而恶意通道不必确认数据包。如果它不确认这些数据包,合法通道似乎将无法转换到FLUSHCOMPLETE状态。结果是无法完成升级,无论是当前还是未来的升级。

重现步骤

POC首先创建合法的transfer v2通道:ibc-0上的channel-0 <-> ibc-1上的channel-0。这部分由中继器处理。

然后在我的应用中打开另外两个通道:ibc-0上的channel-1 <- 我的本地链 ibc-1 || 我的本地链 ibc-0 -> ibc-1上的channel-1。

我提交的接收数据包如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ibc0Cw.ReceivePacketV2(ibc0Path, types.FungibleTokenPacketDataV2{
    Tokens: []types.Token{
        {
            Amount: "1",
            Denom:  types.Denom{Base: "x"},
        },
    },
    Memo:     "",
    Sender:   ibc1Cw.Address(),
    Receiver: ibc0Cw.Address(),
    Forwarding: types.ForwardingPacketData{Hops: []types.Hop{
        {ChannelId: legitChannel, PortId: port}, {ChannelId: ibc0Path.Dest.ChanId, PortId: port},
    }, DestinationMemo: ""},
})

数据包在ibc-0的channel-1上被接收,由于转发功能,它通过channel-0提交到ibc-1的channel-0,然后再次转发到我的ibc-1的channel-1。因此,它不会写入任何确认,但会写入收据。这意味着我们将无法使其超时。最终结果是,ibc-0上的合法channel-0将继续拥有已提交(传输中)的数据包,直到我的ibc-1上的channel-1收到确认为止。而这是不会发生的。因此,受害通道是ibc-0上的channel-0,最终将保留这个承诺。

POC本身并不是一个新场景。它只是展示了可以在合法通道上创建这些永久承诺的场景。

POC中使用的方法定义在:https://github.com/h1uf/ibc-tools/blob/master/chain/wrapper.go

(确保没有simd在运行)

安装Go 检出并构建 https://github.com/cosmos/ibc-go。运行make build并记下simd的路径 下载我的中继器 https://github.com/h1uf/relayer。它与原始版本几乎相同,只是演示脚本被编辑过。你需要运行一个中继器。你可以构建这个或使用合法的中继器。确保rly在路径中。只需要来自 https://github.com/h1uf/relayer/tree/main/examples/demo 文件夹的脚本。将二进制文件路径替换为上一步中记下的simd路径。运行./dev-env 同时检出 https://github.com/h1uf/ibc-tools。将附件的lock_upgrade.go放到根目录。确保你的路径指向正确的数据文件夹,并且你的主路径是当前目录。它将从链数据中解析种子短语并将密钥存储在当前目录。 运行应用程序

它通过我的代理链建立channel-1到channel-1的连接。所以在这行 https://gist.github.com/unknownfeature/b5c7323df65300a4699a3fa5471e9153#file-gistfile1-txt-L79 之前的所有内容只是建立通道。然后在ibc-0的channel-1上接收数据包。如果你切换到中继器,几分钟后就会开始看到没有确认。但这些都是预期的。只是似乎不预期的是我的通道不会写入确认。似乎并不预期承诺可以永久保持已提交状态(除非我遗漏了除了提到的两个地方之外可以删除它们的地方)。似乎并未认识到通过转发功能的这一部分引入了对潜在恶意通道的依赖,即它等待来自另一个通道的确认。

解决方法

据我所见,似乎没有。没有其他方法可以删除承诺,而且似乎通道在没有刷新所有数据包的情况下无法变为FLUSHCOMPLETE状态。不过我可能遗漏了什么。

支持材料/参考文献

lock_upgrade.go - POC upgrade.mov - POC视频

影响

通过转发功能,Transfer v2通道可能变得无法升级,该功能允许恶意通道在合法通道上创建未被确认的承诺。

时间线

  • 2024年12月26日,下午3:48 UTC:unknown_feature向Cosmos提交报告
  • 2024年12月26日,下午5:06 UTC:amulet_mizmo将状态更改为已分类
  • 2025年5月20日,下午2:52 UTC:Cosmos向unknown_feature发放赏金
  • 2025年5月20日,下午2:55 UTC:arrb(Cosmos员工)关闭报告并将状态更改为已解决
  • 2025年5月30日,下午2:12 UTC:unknown_feature请求HackerOne支持调解
  • 2025年5月31日,下午12:45 UTC:unknown_feature取消调解
  • 2025年5月31日,下午12:46 UTC:unknown_feature请求披露此报告
  • 2025年6月30日,下午12:46 UTC:报告被披露

严重程度:低(0.1 ~ 3.9) 弱点:业务逻辑错误 CVE ID:无 赏金:隐藏

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计