Raisebox Faucet

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

Unbounded Gas Forwarding

Root + Impact

Description

The claimFaucetTokens() function in the RaiseBoxFaucet contract uses an external call to transfer Sepolia ETH to the claimant via faucetClaimer.call{value: sepEthAmountToDrip}(""). This call forwards all available gas (minus the gas already consumed) to the recipient’s receive() or fallback() function. This unbounded gas forwarding exacerbates the existing reentrancy vulnerability by allowing a malicious contract to execute complex logic during reentrant calls, increasing the potential for abuse

// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

The unbounded gas allows a malicious contract to perform multiple reentrant calls to claimFaucetTokens(), claiming multiple ETH drips sepEthAmountToDrip and token transfers faucetDrip in a single transaction, bypassing the 3-day cooldown CLAIM_COOLDOWN and daily claim limit dailyClaimLimit.

A malicious contract can execute computationally expensive operations during the receive() or fallback() function, consuming significant gas while still re-entering the contract, potentially draining tokens and ETH more effectively

Proof of Concept

1.Deploy attacker contract

2.Run attack function

contract Attacker {
RaiseBoxFaucet faucet;
uint256 public reentryCount = 0;
constructor(RaiseBoxFaucet _faucet) {
faucet = _faucet;
}
receive() external payable {
if (gasleft() > 100000 && reentryCount < 50) { // Arbitrary limit for demo
reentryCount++;
// Perform complex operations to consume gas
for (uint256 i = 0; i < 100; i++) {
keccak256(abi.encodePacked(block.timestamp, i)); // Gas-intensive
}
faucet.claimFaucetTokens(); // Re-enter
}
}
function attack() external {
faucet.claimFaucetTokens();
}
}

Recommended Mitigation

To mitigate the unbounded gas forwarding vulnerability and reduce the impact of reentrancy

function claimFaucetTokens() public {
// Checks
faucetClaimer = msg.sender;
// (lastClaimTime[faucetClaimer] == 0);
if (block.timestamp < (lastClaimTime[faucetClaimer] + CLAIM_COOLDOWN)) { //check drip time
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, gas: 3000}("");
- (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
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}
Updates

Lead Judging Commences

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