The claimFaucetTokens function in the RaiseBoxFaucet contract lacks a check to prevent smart contracts from calling it, allowing malicious contracts to exploit a reentrancy vulnerability. The function includes an external call to transfer ETH ((bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("")), which triggers the receive or fallback function of the recipient if it is a contract. A malicious contract can reenter claimFaucetTokens during this call, before state updates such as hasClaimedEth[faucetClaimer], dailyClaimCount, or lastClaimTime[faucetClaimer] are performed.
The only caller validation checks if the faucetClaimer (msg.sender) is not address(0), address(this), or the contract owner:
This check fails to restrict other smart contracts, violating the intended design where only Externally Owned Accounts (EOAs) should claim faucet tokens and ETH. As a result, a malicious contract can repeatedly call claimFaucetTokens within its receive function, bypassing daily limits (dailyClaimLimit, dailySepEthCap) and cooldowns (CLAIM_COOLDOWN), potentially draining the contract’s token balances.
Invariant Violation: The contract intends to drip exactly 1000 tokens per user every 3 days (Token Drip Invariant). The reentrancy allows a user to claim 2000 tokens in a single transaction, breaking this invariant and undermining fair distribution.
High
This vulnerability allows a malicious smart contract to exploit reentrancy, leading to unauthorized claims of faucet tokens. In a testnet context, this can drain the faucet’s entire balance, rendering it unusable for legitimate users. In a mainnet scenario, such a flaw could result in significant financial loss.
Fund Drain: A malicious contract can claim multiple batches of tokens (1000e18 each) in a single transaction, bypassing dailyClaimLimit and dailySepEthCap, potentially draining the contract’s balance.
Protocol Disruption: The faucet becomes unusable for legitimate users, undermining its purpose of providing test tokens for protocol testing.
Loss of Trust: In a testnet, this degrades user experience and protocol adoption. In a mainnet context, it could lead to catastrophic financial loss.
No Direct Access Control Bypass: The owner-only functions remain secure, but the core claiming functionality is compromised.
Manual code review using VS Code with Solidity extensions.
Foundry for Proof of Concept testing (simulating reentrancy with a malicious contract).
To prevent reentrancy and restrict claims to EOAs, implement the following:
Add EOA-Only Check:
Add a check to ensure the caller is an EOA, not a contract:
Note: Using tx.origin is generally discouraged in mainnet due to phishing risks, but it is acceptable for a testnet faucet to restrict contract callers.
Use ReentrancyGuard:
Import OpenZeppelin’s ReentrancyGuard and apply the nonReentrant modifier to claimFaucetTokens:
This ensures the function cannot be reentered, preventing multiple claims in a single transaction.
Best Practice:
Combine both mitigations for defense-in-depth: use the EOA check to enforce the faucet’s design intent and ReentrancyGuard to protect against reentrancy, especially if the contract is adapted for mainnet use.
The following Foundry test demonstrates the reentrancy vulnerability by deploying a malicious contract that reenters in claimFaucetTokens and claiming multiple batches of tokens in a single transaction.
Run the test with: forge test --mt test_ReentrancyVulnerability -vvv.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.