Contract Reference: RaiseBoxFaucet.sol
The claimFaucetTokens function is vulnerable to reentrancy due to an external ETH transfer (faucetClaimer.call{value: sepEthAmountToDrip}) before updating critical state variables (lastClaimTime, dailyClaimCount) and executing the token transfer (_transfer), violating the Checks-Effects-Interactions (CEI) pattern. A malicious contract can reenter during its receive or fallback function, passing the cooldown and daily claim limit checks (since lastClaimTime[msg.sender] and dailyClaimCount are not yet updated), claiming an extra faucetDrip of tokens in a single transaction. The ETH drip is not repeated due to hasClaimedEth being set before the call, and subsequent reentrant calls fail due to the cooldown. This vulnerability manifests only during the first claim when ETH is dripped (!hasClaimedEth[msg.sender] && !sepEthDripsPaused and other conditions are met).
Invariant Violation: The contract intends to drip exactly 1000 tokens per user every 3 days (Token Drip Invariant). The reentrancy allows a user to claim 2000 tokens in a single transaction, breaking this invariant and undermining fair distribution.
High
Likelihood of Exploitation: High. The vulnerability is straightforward to exploit by deploying a malicious contract with a receive or fallback function that reenters claimFaucetTokens. As a public Sepolia faucet, it’s accessible to anyone, and attackers can deploy multiple contracts to maximize exploitation within the daily claim limit (dailyClaimLimit = 100). The low technical barrier and lack of authentication make it an attractive target.
Potential Consequences: Attackers can claim double the intended tokens (2x faucetDrip = 200e18) per contract address, potentially exhausting the faucet’s token supply (1000000e18) and daily claim limit faster than intended. This disrupts the faucet’s purpose of providing fair access to test tokens/ETH for developers, as legitimate users may be unable to claim due to depleted resources.
Contextual Factors: The testnet environment reduces financial risk, but the public nature of the faucet increases the likelihood of coordinated attacks (e.g., bot-driven contract deployments). The vulnerability is constrained by the daily claim limit and the requirement for a first-time claim with ETH dripping enabled, limiting the scale of abuse.
Financial Loss: An attacker can claim an extra faucetDrip amount of tokens per exploiting contract address, potentially draining the faucet's token supply faster than intended.
Bypasses Limits: It partially bypasses the per-user cooldown, allowing double claims in one transaction without waiting.
Limited Scope: The exploit is capped at one extra claim per address (double total) and requires deploying a contract that hasn't claimed before. It doesn't allow unlimited reentrancy due to the cooldown enforcement on subsequent calls. No ETH is duplicated because hasClaimedEth and dailyDrips are updated before the call. If sepEthDripsPaused is true or the ETH cap/balance is insufficient, no ETH is sent, preventing reentrancy.
Real-World Risk: As a public faucet on Sepolia, malicious actors could deploy multiple contracts to amplify the impact, consuming the daily claim limit and depriving legitimate users.
Manual review.
Foundry: Used to develop and test the PoC, verifying the exploit.
Implement CEI Strictly: Move all state updates (lastClaimTime[faucetClaimer] = block.timestamp;, dailyClaimCount++;) and the token transfer (_transfer) before the ETH transfer logic. Ensure the daily claim reset logic is also placed appropriately to avoid inconsistencies.
Add Reentrancy Guard: Use OpenZeppelin's ReentrancyGuard modifier on claimFaucetTokens to prevent reentrant calls.
Prevent Contract Callers (Optional): Add a check to ensure msg.sender is an EOA (e.g., if (msg.sender.code.length > 0) revert;), though note this can be bypassed during contract construction.
Updated Function Structure Example:
The PoC demonstrates a malicious contract reentering claimFaucetTokens during ETH receipt, receiving 2x faucetDrip tokens (2000e18) and 1x sepEthAmountToDrip ETH (0.005 ether). Without reentrancy, it would receive only 1x faucetDrip (1000e18).
Note: The dailyClaimLimit and dailySepEthCap are assumed as 100 and 1 ether, respectively. Verify against the actual contract constants.
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.