lastClaimTime lets malicious contracts claim tokens twice in one txRaiseBoxFaucet::claimFaucetTokens gives first-timers 0.005 SepETH + faucetDrip tokens (let's say, 1000), repeaters get tokens every 3 days. Line 167 cooldown check blocks spam... but the ETH transfer on line 198 is a direct .call{value} to user — classic reentrancy bait.
Problem: hasClaimedEth updates before the call (good, blocks double ETH), but lastClaimTime updates way after on line 227. Malicious contract's receive() reenters, passes cooldown check (still 0), grabs second token batch. Boom — 2x tokens, same ETH.
Any first-timer can deploy this malicious contract—zero complexity
Hits every attacker's first claim automatically
2x token drain per attack — faster balance depletion
Normal users shut out sooner when tokens run dry
Trust wrecked: "Why did the attacker get double my claim?!"
Here's exactly how the exploit works, step by step:
Setup: Deploy RaiseBoxFaucet, fund with 0.01 ETH
Malicious Contract: Deploy MaliciousClaimer with receive() → auto-calls claim() on ETH deposit
The Reentrancy Exploit:
Malicious contract calls claim() → RaiseBoxFaucet::claimFaucetTokens(). First-timer, lastClaimTime[faucetClaimer] = 0, passes cooldown check
!hasClaimedEth[faucetClaimer] passes, sets hasClaimedEth = true
EXTERNAL CALL: Sends 0.005 ETH → triggers receive() → calls claimFaucetTokens() AGAIN
Reentry: lastClaimTime still 0, passes cooldown! Skips ETH (already true), but transfers 1000 tokens
Sets lastClaimTime[faucetClaimer] = block.timestamp (finally)
Original call resumes: Sets lastClaimTime[faucetClaimer] = block.timestamp (pointless), transfers 2nd 1000 tokens
Result: 2000 tokens + 0.005 ETH from ONE call!
Add MaliciousClaimer to the end of RaiseBoxFaucet.t.sol:
Now, add this test case:
Run the above test using the command:
Logs:
It's better to update the lastClaimTime[faucetClaimer] right after the cooldown check on line 167
Additionally, the contract can also implement a pull-based mechanism to send first-time sepEth rewards, rather than using a push-based mechanism, which requires an external call to be made.
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.