Smart contract works by verifing that specific treasure hash has not been claimed yet before distributing the rewards to the recipient.
The issue is a typrographical error in the 'Claim' function duplicate-claim guard.
The duplicate-claim guard read from the uinitialized immutable state variable '_treasureHash', which always evaluates to bytes32(0), instead if the function parameter 'treasureHash'. Because of claimed[bytes32(0)] is always false, the guard never triggers and the same proof can be submitted an unlimited number of times.
Likelihood:
Any attacker who observe a single successful 'claim' transaction on chain can immediately copies the proof and treasure hash and resubmit them repeatedly. It does not require no special knowledge or tooling beyond a basic script.
'claimsCount' is incremented each time, the attacker can loop until 'claimsCount >= MAX_TREASURES' is hit, draining the full contract balance in one automated sequence of transactions.
Impact:
Total loss of all ETH held by the 'TreasureHunt' contract (10 treasures x 10 ETH each).
Complete breakdown of hunt mechanics. A single stolen or observed proof replaces all 10 legitimate treasure finds and claims every reward.
The root cause of that '_treasuereHash' is an uninitailized immutable state variable that permanently holds bytes32(0). This mean claimed[bytes32(0)], which is always false. the guard therefore never reverts, no matter how many times the same 'treasureHash' is submitted.
The attack flow is straightforward:
A legitimate user submits a valid 'claim' transaction. This is the only hard step for finding the treasure.
Once finding the treasure the attacker observes that transaction on-chain or in the mempool and copies the proof and treasure hash.
The attacker call claim() repeatedly with those copied hash. Each call passes the guard because claimed[Bytes32(0)] never become true while the 'claimsCount' increments towards 'MAX_TREASURE'.
Once 'claimsCount >= MAX_TREASURES', the loop ends but by then attacker has drained the full contract balance.
Replace the unintialized state variable '_treasureHash' with the function parameter 'treasureHash' in the duplicate-claim guard.
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.