The claimOnBehalf function uses a contextless signature hashing scheme. By not including the chainId and the verifyingContract address in the signed hash, it allows a valid signature to be captured and replayed on a different chain or contract, leading to the theft of a user's airdropped NFT.
Normal Behavior: A user, who may be unable to pay for gas, signs a message containing their claim details. A third-party relayer submits this signature to the claimOnBehalf function. The contract verifies the signature belongs to the recipient and mints the Snowman NFT to them.
The Issue: The cryptographic hash that the user signs is not unique to the contract or the chain. An attacker can trick a user into signing a message for what they believe is a testnet claim. The attacker can then take this signature—which is just a signature of the claim data—and submit it to the mainnet contract to steal the user's real NFT. The mainnet contract approves it because the signature is mathematically valid for the data payload.
Likelihood:
When an attacker tricks a user into signing a message on a phishing site or a testnet.
When a user signs a message for any other airdrop that uses the same insecure signature scheme.
Impact:
Airdropped NFTs can be claimed by a malicious relayer without the user's consent for the mainnet transaction.
Breaks the fundamental security assumption of signature-based claims.
An attacker convinces the victim (Alice) to sign a message to claim her airdrop on a "testnet version" of the application. The hash she signs is keccak256(abi.encodePacked(alice_address, amount)). The attacker captures this signature (v,r,s). Since this hash contains no information about the chain or the contract it's for, the attacker can simply submit the same signature to the real SnowmanAirdrop contract on mainnet. The mainnet contract will see it as a valid authorization from Alice and process the claim.
To prevent all forms of replay attacks, the contract must adopt the EIP-712 standard. This standard creates a unique signature that is cryptographically bound to the specific contract instance and blockchain. This is achieved by creating a domain separator containing the chainId and verifyingContract address, which is mixed into the final hash. A signature created for the testnet contract will now be completely invalid on the mainnet contract, and vice-versa.
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.