The claim() function is designed to verify a ZK proof and dispense a 10 ETH reward for finding a treasure. To prevent double-claiming, it checks if the treasure has already been claimed. However, a typographical error causes the function to check the claim status of an uninitialized state variable (_treasureHash) instead of the user-provided argument (treasureHash).
Because _treasureHash is never initialized in the constructor, it defaults to bytes32(0). When a user calls claim(), the check evaluates claimed[bytes32(0)], which will always pass. Later in the function, the contract correctly marks the user-provided treasureHash as claimed. Because the check and the state update use different variables, the state update has no effect on future checks. An attacker can submit the exact same valid proof and treasureHash multiple times, bypassing the check and draining the contract.
Likelihood:
Any user who legitimately finds a single treasure (or obtains a valid proof from the mempool) can execute this attack.
The vulnerability requires no special preconditions other than possessing one valid ZK proof.
Impact:
The attacker can repeatedly call claim() in a loop until the contract is completely drained of all ETH.
The intended MAX_TREASURES limit (10) will be reached, but the attacker will receive all 100 ETH instead of the intended 10 ETH per treasure, stealing funds meant for other participants.
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.