这一讲,我们将介绍选择器碰撞攻击,它是导致跨链桥 Poly Network 被黑的原因之一。在2021年8月,Poly Network在ETH,BSC,和Polygon上的跨链桥合约被盗,损失高达6.11亿美元。这是2021年最大的区块链黑客事件,也是历史被盗金额榜单上第2名,仅次于 Ronin 桥黑客事件。
选择器碰撞
以太坊智能合约中,函数选择器是函数签名 "
由于函数选择器只有4字节,非常短,很容易被碰撞出来:即我们很容易找到两个不同的函数,但是他们有着相同的函数选择器。比如transferFrom(address,address,uint256)和gasprice_bit_ether(int128)有着相同的选择器:0x23b872dd。当然你也可以写个脚本暴力破解。
大家可以用这两个网站来查同一个选择器对应的不同函数:
你也可以使用下面的Power Clash工具进行暴力破解:
- PowerClash: https://github.com/AmazingAng/power-clash
相比之下,钱包的公钥有256字节,被碰撞出来的概率几乎为0,非常安全。
漏洞合约例子
漏洞合约
下面我们来看一下有漏洞的合约例子。SelectorClash合约有1个状态变量 solved,初始化为false,攻击者需要将它改为true。合约主要有2个函数,函数名沿用自 Poly Network 漏洞合约。
-
putCurEpochConPubKeyBytes() :攻击者调用这个函数后,就可以将solved改为true,完成攻击。但是这个函数检查msg.sender == address(this),因此调用者必须为合约本身,我们需要看下其他函数。
-
executeCrossChainTx() :通过它可以调用合约内的函数,但是函数参数的类型和目标函数不太一样:目标函数的参数为(bytes),而这里调用的函数参数为(bytes,bytes,uint64)。
contract SelectorClash { bool public solved; // 攻击是否成功 // 攻击者需要调用这个函数,但是调用者 msg.sender 必须是本合约。 function putCurEpochConPubKeyBytes(bytes memory _bytes) public { require(msg.sender == address(this), "Not Owner"); solved = true; } // 有漏洞,攻击者可以通过改变 _method 变量碰撞函数选择器,调用目标函数并完成攻击。 function executeCrossChainTx(bytes memory _method, bytes memory _bytes, bytes memory _bytes1, uint64 _num) public returns(bool success){ (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes, _bytes1, _num))); } }
攻击方法
我们的目标是利用executeCrossChainTx()函数调用合约中的putCurEpochConPubKeyBytes(),目标函数的选择器为:0x41973cd9。观察到executeCrossChainTx()中是利用_method参数和"(bytes,bytes,uint64)"作为函数签名计算的选择器。因此,我们只需要选择恰当的_method,让这里算出的选择器等于0x41973cd9,通过选择器碰撞调用目标函数。
Poly Network黑客事件中,黑客碰撞出的_method为 f1121318093,即f1121318093(bytes,bytes,uint64)的哈希前4位也是0x41973cd9,可以成功的调用函数。接下来我们要做的就是将0x41973cd9转换为bytes类型:0x6631313231333138303933,然后作为参数输入到executeCrossChainTx()中。executeCrossChainTx()函数另3个参数不重要,都填 0x 就可以。
Remix演示
- 部署SelectorClash合约。
- 调用executeCrossChainTx(),参数填0x6631313231333138303933,0x,0x,0x,发起攻击。
- 查看solved变量的值,被修改为ture,攻击成功。
总结
这一讲,我们介绍了选择器碰撞攻击,它是导致跨链桥 Poly Network 被黑 6.1 亿美金的的原因之一。这个攻击告诉了我们:
- 函数选择器很容易被碰撞,即使改变参数类型,依然能构造出具有相同选择器的函数。
- 管理好合约函数的权限,确保拥有特殊权限的合约的函数不能被用户调用。
转载:https://mirror.xyz/wtfacademy.eth/5rwcsBZzphdlKZj4MoIpn8aqwQ1MzQ8qy50ZEGNU_HU