SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
Submission Details
Impact: medium
Likelihood: medium

withdraw() has no access control. anyone can trigger owner withdrawal.

Author Revealed upon completion

ROOT: ** **contracts/src/TreasureHunt.solwithdraw() is missing onlyOwner modifier present on all other admin functions
IMPACT: Anyone can trigger the owner withdrawal, griefing the owner and wasting their gas

Description

  • The withdraw() function is intended to let the owner recover leftover funds after the hunt concludes. All other admin functions use onlyOwner or an equivalent require check.

// @> missing onlyOwner — compare with pause(), unpause(), fund(), emergencyWithdraw()
function withdraw() external {
require(claimsCount >= MAX_TREASURES, "HUNT_NOT_OVER");
uint256 balance = address(this).balance;
require(balance > 0, "NO_FUNDS_TO_WITHDRAW");
// @> funds go to owner regardless of who called
(bool sent, ) = owner.call{value: balance}("");
require(sent, "ETH_TRANSFER_FAILED");
emit Withdrawn(balance, address(this).balance);
}

Risk

Likelihood:

  • Any address calls withdraw() after all treasures are claimed — the condition claimsCount >= MAX_TREASURES is publicly readable so anyone knows when it becomes callable

  • A griefing actor front-runs the owner's withdrawal transaction, forcing the owner to pay gas for a transaction that never executes

Impact:

  • No financial loss funds always reach the owner

  • Owner loses control over withdrawal timing and wastes gas if front-run

Proof of Concept

Anyone can call this once claimsCount reaches MAX_TREASURES

ITreasureHunt(hunt).withdraw(); // succeeds, sends full balance to owner

Recommended Mitigation

- function withdraw() external {
+ function withdraw() external onlyOwner {
require(claimsCount >= MAX_TREASURES, "HUNT_NOT_OVER");
uint256 balance = address(this).balance;
require(balance > 0, "NO_FUNDS_TO_WITHDRAW");
(bool sent, ) = owner.call{value: balance}("");
require(sent, "ETH_TRANSFER_FAILED");
emit Withdrawn(balance, address(this).balance);
}

Support

FAQs

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

Give us feedback!