In claimFaucetTokens, qualifying first-time claims check the daily ETH cap and balance, update hasClaimedEth and dailyDrips, then perform the ETH transfer via (bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}(""). If the recipient is a malicious contract, its receive() can reenter claimFaucetTokens. The inner call passes checks (e.g., cooldown uses the same timestamp, passing for new claims) and executes the token transfer (_transfer), emitting events, but skips ETH drip due to hasClaimedEth already set. The outer call then completes, performing another token transfer.
This results in two token claims per transaction (outer and inner), bypassing the intent of one claim per cooldown period. While ETH drip is limited (inner skips it), the double token issuance erodes the faucet's supply and daily count integrity. The vulnerability stems from the call's position after state updates but before final interactions, allowing unchecked reexecution.
Likelihood:
Medium: Requires a malicious contract as claimant (common in attacks), but no complex setup beyond deploying and calling.
Cooldown and state updates limit depth, but single reentry is sufficient for abuse.
Impact:
Medium: Enables double token claims per transaction, accelerating supply depletion and partially bypassing daily limits, leading to faster faucet exhaustion.
No ETH double-dip (inner skips drip), but repeated attacks could drain tokens, denying service to legitimate users.
The following Foundry test deploys a malicious contract that reenters claimFaucetTokens during receive(). The outer call updates state and transfers ETH, triggering reentry. The inner call skips ETH but performs a token transfer, followed by the outer's token transfer, resulting in two claims in one transaction.
Add the following to the RaiseBoxFaucetTest.t.sol test, including the MaliciousClaimer contract:
Setup: Deploys MaliciousClaimer with the faucet address and calls claimFaucetTokens as the malicious contract.
Outer Call: Updates hasClaimedEth and dailyDrips, then transfers ETH, triggering receive().
Reentrant Call: receive() re-calls claimFaucetTokens. The inner call skips ETH (due to hasClaimedEth=true) but passes cooldown (same timestamp) and performs a token transfer, incrementing dailyClaimCount to 1.
Outer Continuation: Completes with another token transfer, incrementing dailyClaimCount to 2, and emits SepEthDripped for the ETH.
Result: The malicious contract receives 2 token transfers (2e21 tokens) and 1 ETH drip (5e15 wei) in one transaction, confirming double claim without cooldown enforcement. The test passes, proving the vulnerability allows unauthorized double issuance.
Introduce OpenZeppelin's ReentrancyGuard modifier on claimFaucetTokens to prevent reentry, or move the ETH transfer after all state updates and use Address.sendValue for safer external calls. Alternatively, perform the token transfer before the ETH call to limit reentry impact.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.