关键代码深度解析 BNB Chain 遭受攻击事件
Beosin
2022-10-09 19:47
订阅此专栏
收藏此文章
本文将对 BNB Chain 安全事件继续拆解,把更详细的攻击过程呈现出来。


原文标题:《BNB Chain 遭受攻击事件技术分析持续加码!关键代码深度解析》

撰文:Beosin


北京时间 2022 年 10 月 7 日,据 Beosin EagleEye - Web3 安全预警与监控平台舆情监测显示,BNB Chain 跨链桥「代币中心」(Token Hub)遭遇黑客攻击,在昨天的文章里《BNB Chain 安全事件分析:涉及总金额超 8.5 亿美元》,我们将事件的来龙去脉以及攻击手法分享给大家。


今天,我们将对本次事件继续拆解,把更详细的攻击过程呈现出来。


1 漏洞详细分析


首先可以知道的是,BSC Token Hub 是 BNB Beacon Chain(BEP2)和 BSC(BEP20 或 BSC)之间的跨链桥。


在 BSC 与 BNB Beacon Chain 进行跨链时,BSC 会使用预编译合约 0x65 对 BNB Beacon Chain 提交 IAVL 树的 proof 进行验证。而本次的漏洞点就出在了 proof 这里。


IAVL 树的结构


IAVL 树,全称为「Immutable AVL」树。在 IAVL 树中,叶子节点和中间节点的数据结构相同,差别在于节点中具体字段的值不同。


对于叶子节点来说,其中的 size 字段一直为 1, height 字段一直为 0, 并且 value 字段真正存储了对应某个键的值, 而关于左右孩子的字段则为 nil。


对于中间节点来说, size 字段大于 1, height 字段大于 0, value 字段为空, 而 key 字段则等于其右子树中节点的 key 的最小值。


// Node represents a node in a Tree.

type Node struct {

key []byte // 节点的键

value []byte // 叶子节点的值, 如果是中间节点则为 nil

  version   int64  // IAVL 树上首次插入该节点时的版本号

height int8 // 节点的高度. 叶子节点的高度为 0

size int64 // 以当前节点代表的子树包含的叶子节点个数, 叶子节点该值为 1

hash []byte // 上面字段以及 leftHash 和 rightHash 的哈希值

leftHash []byte // 左孩子的哈希值

leftNode *Node // 左孩子的指针

rightHash []byte // 右孩子的哈希值

rightNode *Node // 右孩子的指针

persisted bool // 标记当前节点是否已经持久化到数据库中

}


叶子节点的 key 值是按照从左到右的顺序逐渐增大。中间节点存储右子树叶子节点 key 的最小值。


下图中,每个节点中的数字表示该节点的键 key 字段。

 

图源自 longcpp Github


虽然叶子节点和中间节点复用了相同的数据结构 Node, 但是由于字段值的不同, 两种节点的哈希值计算过程也不相同:


  • 计算叶子节点哈希值: Hash(height||size||version||key||Hash(value))
  • 计算中间节点哈希值: Hash(height||size||version||leftHash||rightHash)


IAVL 树的 Merkle 证明


在 Node 结构中加入左右孩子节点的哈希值,可以对 IAVL 树中存储的键对应的值做存在性证明。如果树中没有相应的键值对也可构建不存在性证明。由于 IAVL 树中仅有叶子节点保存值,所以对于一个键值对的存在性证明就是从树根到相应叶子节点的路径。验证时只需要从叶子节点逐层计算哈希值并将最终得到的哈希值与已知的根节点的哈希值进行比对,如果相等就证明了该键值对在树中确实存在。


下面是区间证明 RangeProof 的结构:


type RangeProof struct {

LeftPath PathToLeaf // 树根到最左侧叶子节点的路径 ( 不含叶子节点 )

InnerNodes []PathToLeaf // 到其它叶子节点的路径 ( 不含叶子节点 )

Leaves []proofLeafNode // Range 包含的所有的叶子节点


rootVerified bool // 已经用合法的根哈希验证过该 RangeProof

rootHash []byte // 当前 RangeProof 对应的根节点哈希,需 rootVerified 为 true

treeEnd bool // 最末叶子节点是树的最右叶子节点,需 rootVerified 为 true

}

type PathToLeaf []proofInnerNode


type proofInnerNode struct {

Height int8 `json:"height"`

Size int64 `json:"size"`

Version int64 `json:"version"`

Left []byte `json:"left"` // 左孩子节点哈希值

Right []byte `json:"right"`// 右孩子节点哈希值

}


type proofLeafNode struct {

Key cmn.HexBytes `json:"key"`

ValueHash cmn.HexBytes `json:"value"`

Version int64 `json:"version"`

}


其中 PathToLeaf 是定义在文件 iavl/proof_path.go 中的 ProofInnerNode 的数组,表示从根节点到某个叶子节点的路径,不包括叶子节点,,而 proofInnerNode 是定义在文件 iavl/proof.go 中的结构体,仅包括在哈希值计算在过程中涉及到的中间节点的字段值,该文件中同样定义了结构体 proofLeafNode,由于叶子节点的 height 和 size 字段都是固定值,需要包含在结构中。而其中的 ValueHash 则是叶子节点存储的值的哈希值。


但是在 cosmos 的 IAVL proof.go 实现时,出现了漏洞:


在下图代码中,可以看到第 79 行,当 pin.Left 为空时,会计算 pin.Right 的值,而在第 88 行,当 pin.Left 不为空时,仅仅计算 pin.Left 的值,并未考虑 pin.Right 的值是否为空,且不为空的情况下未参与 hash 计算。即这里实现时,默认只存在左子节点 pin.Left 或右子节点 pin.Right,并未考虑到两者均存在的情况。这就导致攻击者在 pin.Left 不为空的情况下,构造了带有攻击载荷的 pin.Right,即新增了一个叶子节点,而该节点未参与到 rootHash 的计算。



同时,为了满足 InnerNode 与 leaves 的校验,在添加叶子节点后,攻击者还新增了一个空的 InnerNode 节点。



最终,使得在保持 rootHash 不变的情况下,生成了新的 proof。


对此,我们找到了 BSC 上同 BNB Beacon Chain 上区块高度为 110217401 的原始交易:


https://bscscan.com/tx/0x79575ff791606ef2c7d69f430d1fee1c25ef8d56275da94e6ac49c9c4cc5f433

 


其原始交易的 proof:


0x0ab3010a066961766c3a76120e00000100380200000000000000021a980196010a93010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a2b0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef631a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7340ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143


而攻击者攻击成功交易的 proof 为:


0x0a8d020a066961766c3a76120e00000100380200000000010dd9ac1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20121a1f9c4eca726c725796c5375fc4158986ced08e498dc8268ef94d8ed1891612001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd9ac12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143


对比可知,两者的 proof 大小相差不多,且攻击者构建的 proof 确实比原始的 proof 多了一组信息。



2 漏洞修复


BSC VM 代码更新,添加攻击者的黑名单:



Cosmos 的 IAVL proof.go 中对漏洞进行了修复:当 pin.Left 和 pin.Right 同时都不为空时,返回错误。



通过本次事件,我们注意到跨链桥往往因为业务复杂,代码量较大,在进行编码实现时容易出现漏洞;同时,项目中引用的第三方组件安全也是造成安全漏洞的重要原因之一。


Beosin 安全团队建议:


1)项目中的核心代码使用第三方组件时,应进行详尽的安全检查或邀请专业的安全团队进行审查;

2)项目方在项目上线前建议进行完整的安全审计。


参考链接:
https://github.com/longcpp/CryptoInAction/blob/master/cosmos-coinex/iavl.md

【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。

相关Wiki
Beosin
数据请求中
查看更多

推荐专栏

数据请求中
在 App 打开