The withdraw() function is intended to be an administrative action that allows the contract owner to sweep the treasury balance once the treasure hunt has concluded (when claimsCount meets or exceeds MAX_TREASURES).
The function is missing an access control modifier (such as onlyOwner), allowing any external account to trigger the transfer of the contract's entire Ether balance to the owner's address.
Likelihood:
The function visibility is set to external and contains no caller validation, making it accessible to any user on the network
Blockchain monitoring bots (griefers) constantly scan for unprotected functions to execute them as soon as the state-based requirements (claimsCount) are satisfied.
Impact:
An unauthorized party can force a withdrawal at an inconvenient time for the owner, potentially causing issues with tax accounting, fund management, or cold-storage readiness.
If the owner is a smart contract (like a multisig) that is temporarily unable to receive ETH due to a configuration error, an attacker can intentionally trigger the withdrawal to "lock" the function in a failed state or force the owner to deal with the failure prematurely.
The PoC demonstrates that the withdraw() function relies entirely on system state rather than caller identity.
State Bypassing: The test uses low-level storage manipulation (vm.store) to force the contract into a "Post-Game" state. This proves that the vulnerability is not tied to the game logic itself, but exists as a standalone flaw once the game's conditions are met.
Access Verification: By executing the function from a randomly generated address (the attacker), the PoC confirms that the Ethereum Virtual Machine (EVM) finds no restrictions on who can initiate the transaction.
Resulting Action: The PoC concludes by verifying that the contract balance was successfully moved. Even though the funds were sent to the owner, the fact that an unauthorized third party could dictate the timing and execution of this high-value transfer is the core of the proof.
By adding the onlyOwner modifier, the contract introduces a mandatory check at the very beginning of the function execution. This check compares the msg.sender (the person calling the function) against the stored owner address.
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.
The contest is complete and the rewards are being distributed.