Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
Submission Details
Impact: high
Likelihood: high

Owner Can Bypass Claim Restriction and Drain Faucet Tokens

Author Revealed upon completion

Root + Impact

Description

  • DAccording to the protocol documentation, the owner is explicitly restricted from claiming faucet tokens. The owner's role is limited to deploying the contract, minting tokens, burning tokens, adjusting claim limits, and refilling the ETH balance.

  • The claimFaucetTokens() function does not implement any access control to prevent the owner from calling it. This allows the owner to bypass the intended restriction and claim tokens repeatedly, potentially draining the faucet.

// Root cause in the codebase with @> marks to highlight the relevant sectionfunction claimFaucetTokens() external {
require(block.timestamp >= lastClaim[msg.sender] + 3 days, "Claim too soon");
// @> Missing: require(msg.sender != owner, "Owner cannot claim");
lastClaim[msg.sender] = block.timestamp;
_mint(msg.sender, 1000 * 10**18);
if (!hasClaimedEth[msg.sender]) {
hasClaimedEth[msg.sender] = true;
(bool success, ) = msg.sender.call{value: 0.005 ether}("");
require(success, "ETH transfer failed");
}
}

Risk

Likelihood:

  • The owner has access to the contract's private key and can call any public function

  • The owner has access to the contract's private key and can call any public function

  • The owner can claim every 3 days indefinitely

Impact:

  • Violates the core design specification that owner cannot claim tokens

Owner can drain faucet tokens intended for legitimate users

  • Reduces token availability for actual testers

  • Undermines trust in the protocol

  • Owner gains unfair advantage over regular users

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/RaiseBoxFaucet.sol";
contract OwnerClaimTest is Test {
RaiseBoxFaucet faucet;
address owner = address(1);
function setUp() public {
vm.startPrank(owner);
faucet = new RaiseBoxFaucet();
faucet.mint(owner, 1_000_000 * 10**18); // Mint initial supply
vm.deal(address(faucet), 1 ether); // Fund with ETH
vm.stopPrank();
}
function testOwnerCanClaim() public {
vm.startPrank(owner);
// Owner claims tokens (should fail but doesn't)
uint256 balanceBefore = faucet.balanceOf(owner);
faucet.claimFaucetTokens();
uint256 balanceAfter = faucet.balanceOf(owner);
// Owner successfully claimed 1000 tokens
assertEq(balanceAfter - balanceBefore, 1000 * 10**18);
// Owner can claim again after 3 days
vm.warp(block.timestamp + 3 days);
faucet.claimFaucetTokens();
vm.stopPrank();
}
}

Recommended Mitigation

function claimFaucetTokens() external {
+ require(msg.sender != owner(), "Owner cannot claim tokens");
require(block.timestamp >= lastClaim[msg.sender] + 3 days, "Claim too soon");
lastClaim[msg.sender] = block.timestamp;
_mint(msg.sender, 1000 * 10**18);
if (!hasClaimedEth[msg.sender]) {
hasClaimedEth[msg.sender] = true;
(bool success, ) = msg.sender.call{value: 0.005 ether}("");
require(success, "ETH transfer failed");
}
}

Support

FAQs

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