TreasureHunt.withdraw() is intended to be an owner-only function that sweeps the contract balance to the owner once all treasures have been claimed. However, the function lacks the onlyOwner modifier. Any external caller can invoke withdraw() once claimsCount >= MAX_TREASURES, forcing the fund sweep to execute at a time of the attacker's choosing rather than the owner's.
While funds are always sent to owner and cannot be redirected, this breaks the protocol's intended access control and allows a malicious actor to grief the owner by triggering the withdrawal at an unfavourable moment (e.g., front-running the owner's own withdrawal call, or triggering it mid-hunt if the claims count condition is somehow met early via the _treasureHash bypass).
Likelihood:
The function is public with no access restriction; any watcher of the mempool can call it the moment claimsCount reaches MAX_TREASURES
Combined with the _treasureHash bypass (separate Critical finding), claimsCount can be incremented rapidly, making this condition reachable immediately
Impact:
Funds always reach the owner, so there is no theft
The owner loses control over the timing of the withdrawal
An attacker can front-run the owner's withdrawal transaction, denying the owner the ability to choose when to sweep
The `withdraw()` function is intended as an owner-only post-hunt recovery function, but the implementation does not actually enforce any ownership check before transferring the full remaining balance to owner. The function only requires that `claimsCount >= MAX_TREASURES` and that the contract balance is nonzero, after which it sends all ETH to the stored owner address regardless of who called the function. Therefore, the access control on the function itself is incomplete because any external account can trigger the withdrawal path once the hunt is considered over.
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.