Binance BGEO Attack Analysis
Contents
参数检查不严格的锅
漏洞分析
攻击发生在https://bscscan.com/tx/0x9f4ef3cc55b016ea6b867807a09f80d1b2e36f6cd6fccfaf0182f46060332c57
被攻击合约: 0xc342774492b54ce5F8ac662113ED702Fc1b34972
合约的mint
定义如下:
|
|
其中的_mint()
如下:
|
|
在mint()
函数执行了2个检查, 通过检查便可调用_mint()
增加msg.sender的余额
-
检查1:
1 2
require(!txHashes[_txHash], "tx-hash-used"); txHashes[_txHash] = true;
检查传入的_txHash是否被使用过, 避免同一个__txHash被使用多次.
-
检查2:
1
isSigned(_txHash, _amount, _r, _s, _v)
这是一个modifier, 用于检查 _txHash和_amout传入参数是否被正确签名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
modifier isSigned( string memory _txHash, uint256 _amount, bytes32[] memory _r, bytes32[] memory _s, uint8[] memory _v ) { require(checkSignParams(_r, _s, _v), "bad-sign-params"); bytes32 _hash = keccak256( abi.encodePacked(bsc, msg.sender, _txHash, _amount) ); address[] memory _signers = new address[](_r.length); for (uint8 i = 0; i < _r.length; i++) { _signers[i] = ecrecover(_hash, _v[i], _r[i], _s[i]); } require(isSigners(_signers), "bad-signers"); _; } function isSigners(address[] memory _signers) public view returns (bool) { for (uint8 i = 0; i < _signers.length; i++) { if (!_containsSigner(_signers[i])) { return false; } } return true; } function checkSignParams( bytes32[] memory _r, bytes32[] memory _s, uint8[] memory _v ) private view returns (bool) { return (_r.length == _s.length) && (_s.length == _v.length); }
从上面的代码可以看出, 验证签名的逻辑如下: 对于参数_txHash和_amount, 有一组椭圆曲线签名对应的(r, s, v), 通过
ecrecover
函数恢复出签名者的公钥(地址), 并检查签名地址是否包含在预定于的合法签名者列表中(可以理解为白名单)可以将代码简化为:
1 2 3 4 5 6 7 8 9
def isSigned(message, r[],s[],v[]){ allSigners = recovery_signer_addresses_from_r_s_v_array(); for (s in allSigners){ if (s not in whiteList){ return false; } } return true }
这里有两个问题需要注意: 一是默认返回不应该为true, 而应该默认返回false. 二是没有注意数组可能为空, 但数组长度为0时, 代码直接返回true了, 这就绕过了
isSigned
1 2 3 4 5 6 7 8 9
function isSigners(address[] memory _signers) public view returns (bool) { // 问题出在这里: 传入的数组为空时,长度为0, 直接返回true了 for (uint8 i = 0; i < _signers.length; i++) { if (!_containsSigner(_signers[i])) { return false; } } return true; }
POC
|
|
输出:
|
|