Fallback
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Fallback {
mapping(address => uint) public contributions;
address public owner;
constructor() {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
Goal
Withdraw the ETH from the vulnerable smart contract
Exploit
In order to drain the contract from it's funds, we need to be able to call the withdraw() function.
From the code we can see that the withdraw() function, has onlyOwner modifier attached to it, which means that only the account that has created this smart is able to call this function.
So, the next step would be to check if somehow we can modify/change the owner of the contract.
If we look further down the code, we can see that the place were the owner is modified is in the receive() function. From the solidity docs we can see that the receive() function is executed on a call to the contract with empty calldata, this means this function is executed on plain Ether transfers.
Before being able to call the receive() function to change the owner, we must fulfil the require statement inside the function, that is we must call the receive function with some value (ETH) greater than 0, and we must have contribution before calling the function.
To exploit the contract we need to:
- call contribute() function with value less than 0.001 ETH
- send ETH to the Fallback contract, this will trigger the receive() function and therefore change the owner of the contract
- call the withdraw() function, since we are the new owner of the contract, all the balance from the Fallback contract will be transferred to our account