receiver
as EIP-712 Signer in SnowmanAirdrop.sol::_isValidSignature()
functionThe _isValidSignature()
function is intended to verify an EIP-712 signature. It constructs a message hash (getMessageHash
) that includes the receiver
address and an amount
(derived from i_snow.balanceOf(receiver)
). The critical flaw is that the signature is primarily bound only to the receiver
and the amount
, but not to the actual address (msg.sender
) that calls the claimSnowman
function. This creates a significant vulnerability for signature replay and phishing attacks.
Replay Attacks: An attacker can intercept a valid signed message from a legitimate user (victim). Since the signature is not bound to the msg.sender
(the caller), the attacker can then use this victim's signature to execute the claimSnowman
function themselves, potentially controlling aspects of the transaction or timing, even if the NFT is ultimately minted to the victim. The provided Proof of Code demonstrates this perfectly.
Phishing Risk: Attackers can trick a valid wallet holder into signing a seemingly innocuous message (e.g., for a "test claim"). Once signed, the attacker can then replay this signature to claim NFTs or tokens on the victim's behalf, or even for themselves if the receiver
address in the calldata could be manipulated (though current getMessageHash
prevents this for receiver
).
Loss of Expected Tokens/Control: While the current PoC shows the NFT minted for Alice, the attacker Satoshi gains the ability to trigger Alice's claims. In a more complex scenario, if the amount
check was less strict, an attacker could potentially drain funds or claim more than intended.
The getMessageHash
function uses abi.encode(MESSAGE_TYPEHASH, SnowmanClaim({receiver: receiver, amount: amount}))
to create the hash. Notably, msg.sender
(the caller) is absent from this hash.
The provided test_ReplaySignature_Phishing_BySatoshi()
in TestSnowmanAirdrop.t.sol
vividly demonstrates this:
To prevent signature replay attacks and bind the signature to the transaction executor, always include msg.sender
in the EIP-712 typed data hash. Additionally, consider implementing a unique, contract-managed nonce for each signature to prevent replay attacks even if msg.sender
is compromised or bypassed.
Bind to msg.sender
: Modify the SnowmanClaim
struct and getMessageHash
to include the expected msg.sender
of the claimSnowman
function.
Nonce-based Replay Protection: Implement a mapping (mapping(bytes32 => bool) public s_usedNonces;
) to track used nonces. Each signature should include a unique nonce, which is marked as used after a successful claim.
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.