北京时间2022年10月7日,据成都链安鹰眼-区块链安全态势感知平台舆情监测显示,BNB Chain跨链桥“代币中心”(Token Hub)遭遇黑客攻击,在昨天的文章里(Web3第一大黑客事件,攻击涉及总金额超8.5亿美元,BNB Chain遭受攻击分析),我们将事件的来龙去脉以及攻击手法分享给大家。
今天,我们将对本次事件继续拆解,把更详细的攻击过程呈现出来。
首先可以知道的是,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字段。
虽然叶子节点和中间节点复用了相同的数据结构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:
而攻击者攻击成功交易的proof为:
0x0a8d020a066961766c3a76120e00000100380200000000010dd9ac1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20121a1f9c4eca726c725796c5375fc4158986ced08e498dc8268ef94d8ed1891612001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd9ac12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143
对比可知,两者的proof大小相差不多,且攻击者构建的proof确实比原始的proof多了一组信息。
BSC VM代码更新,添加攻击者的黑名单:
Cosmos的IAVL proof.go中对漏洞进行了修复:当 pin.Left 和 pin.Right 同时都不为空时,返回错误。
通过本次事件,我们注意到跨链桥往往因为业务复杂,代码量较大,在进行编码实现时容易出现漏洞;同时,项目中引用的第三方组件安全也是造成安全漏洞的重要原因之一。
成都链安安全团队建议:
1)项目中的核心代码使用第三方组件时,应进行详尽的安全检查或邀请专业的安全团队进行审查;
2)项目方在项目上线前建议进行完整的安全审计。
参考链接:
https://github.com/longcpp/CryptoInAction/blob/master/cosmos-coinex/iavl.md
原文始发于微信公众号(成都链安):BNB Chain遭受攻击事件技术分析持续加码!关键代码深度解析