Normaly claimFaucetTokens() is designed to let each user claim tokens once every 3 days, receiving 1000 faucet tokens and optionally 0.005 ETH on their first claim.
However, because the function sends ETH using a low-level call before updating state variables, it becomes reentrant ,allowing a malicious contract to repeatedly reenter the function before the cooldown (lastClaimedAt) is updated. This can completely drain both token and ETH balances from the faucet.
Likelihood:
Reason 1 : When a malicious user deploys a smart contract with a fallback function that re-calls claimFaucetTokens(), reentrancy occurs immediately when ETH is sent.
Reason 2 : This occurs every time a “first-time” user (new address) claims, as the ETH transfer uses call, which forwards all gas.
Impact:
Impact 1 : Attacker can recursively mint unlimited faucet tokens before lastClaimedAt is updated.
Impact 2 : Attacker can drain all ETH stored in the faucet contract within one transaction.
Explaination :
Prereq: Deploy RaiseBoxFaucet with tokens and some Sepolia ETH; confirm faucetDrip and sepEthAmountToDrip.
Deploy PoC: Deploy ReentrancyExploit with the faucet address.
Execute: Call attack(); when faucet sends ETH the exploit contract receive() reenters claimFaucetTokens() once.
Observed: Reentrant call bypasses cooldown and receives an extra token drip; faucet token balance decreases by multiple faucetDrip amounts in the same transaction.
Verify: Check the transaction trace for nested calls and multiple _transfer/SepEthDripped events; compare balances before/after.
Note: Safe to run on testnets only (Sepolia). The PoC uses one reentry for demonstration; attackers may loop to drain more funds.
Explaination :
Update critical state (cooldowns, hasClaimedEth, daily counters) before any external calls.
Non-reentrant: Add OpenZeppelin ReentrancyGuard and mark claimFaucetTokens() nonReentrant.
Prefer pull: Record claimableSepEth[addr] and let users withdrawSepEth() instead of sending ETH inside the claim.
Day counters: Reset daily caps only on a day rollover (use block.timestamp / 1 days or dailyDripsByDay[day]).
Tests: Add unit tests (attacker contract) and CI checks to ensure caps and cooldowns hold.
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.