Raisebox Faucet

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

Critical Vulnerability: Bypass of Daily Sepolia ETH Cap via Re-entrancy or Repeated Claims

Root Cause

The Contract resets dailyDrips = 0 in the else block of the ETH drip logic:

Description

if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
// ... (ETH drip logic)
} else {
dailyDrips = 0; // ⚠️ DANGEROUS RESET
}

Risk

  • Every time a user who has already claimed ETH calls claimFaucetTokens(), the contract resets dailyDrips to 0.

  • This resets the daily ETH drip counter, effectively bypassing the dailySepEthCap.

Exploitation Scenarios

  1. Attacker claims tokens once → receives ETH, hasClaimedEth\[attacker] = true, dailyDrips = 0.005 ether.

  2. Attacker waits < 24 hours (so lastDripDay is unchanged).

  3. Attacker calls claimFaucetTokens() again (after 3-day cooldown is irrelevant—they can wait or use multiple addresses).

  • Since hasClaimedEth\[attacker] == true, the else block runs: dailyDrips = 0.

  1. Now, a new victim (or the attacker using a second address) claims for the first time.

  • The contract checks: dailyDrips (0) + sepEthAmountToDrip <= dailySepEthCap → passes.

  • Victim gets ETH, dailyDrips is incremented again.

  1. Repeat steps 3–4 indefinitely to drain all ETH in the contract, ignoring the daily cap.

Even worse: If the attacker controls two addresses, they can alternate claims to reset dailyDrips before every first-time claim, making the dailySepEthCap completely ineffective.

Why This Happens

  • The else { dailyDrips = 0; } line assumes that non-first-time claims should reset the ETH drip counter, which is logically incorrect.

  • The daily ETH cap should only reset once per day (based on lastDripDay), not on every non-ETH claim.

Recommended Mitigation

Remove the erroneous dailyDrips = 0 reset in the else block. The dailyDrips counter should only be reset once per day in the time-check block:

Vulnerable Code

if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
// ... ETH drip logic
} else {
dailyDrips = 0; // 🚫 REMOVE THIS
}

Fixed Code

// Remove the entire `else { dailyDrips = 0; }` block.
// Ensure dailyDrips is only reset once per day:
uint256 currentDay = block.timestamp / 1 days; // or 24 hours
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
// Note: dailyClaimCount reset is handled separately below
}

Also: The dailyClaimCount reset logic uses lastFaucetDripDay with 1 days, but the ETH drip uses 24 hours — standardize to one unit (e.g., 1 days).

Proof of Concept (Exploit)

This can be tested through a foundry test.

// In a Foundry test:
function testExploitDailyEthCapBypass() public {
// Deploy contracts...
// Attacker claims first time → gets ETH
vm.prank(attacker1);
faucet.claimFaucetTokens();
// Wait < 24h, > 3 days (or use second address)
vm.warp(block.timestamp + 1 hours);
// Attacker claims again (already claimed ETH)
vm.prank(attacker1);
faucet.claimFaucetTokens(); // This resets dailyDrips to 0!
// New user claims → gets ETH AGAIN despite daily cap
vm.prank(victim);
faucet.claimFaucetTokens(); // Should fail if cap was enforced, but doesn't!
}
Updates

Lead Judging Commences

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

dailyDrips Reset Bug

Support

FAQs

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