fulfillRandomWords()
does not verify its input parameter request_id
. This could lead to a replay attack where a previous transaction from the VRF coordinator is replayed to influence the random numbers that determine the raffle winner and NFT rarity.
The first call to the VRF coordinator results in a unique request_id
. Later, the VRF coordinator calls fulfillRandomWords()
with the same request_id
as a function argument. It is considered best practice to verify the correct request_id
( https://docs.chain.link/vrf/v2/security#use-requestid-to-match-randomness-requests-with-their-fulfillment-in-order , last accessed on 3/14/2024).
Without this check, a previous transaction from the VRF coordinator to the same raffle contract could be replayed. This could be a transaction that was pending in the mempool at one point but got dropped because of a too low gas price, for example, or a transaction that was included in a hard-forked network that allows for cross-chain replay attacks (e.g., Ethereum versus Ethereum Classic).
A malicious user could send such a transaction with known random numbers if they benefit the malicious user before the VRF coordinator could send the transaction with the legitimate request_id
.
Low: According to the specification, the raffle contract is planned to be deployed on Ethereum, Arbitrum, and zksync which have different chain IDs preventing cross-chain replay attacks. However, if there are hard forks of these chains in the future, this replay attack could become possible.
The case of dropped transactions from the VRF coordinator that could be used for a replay attack is rare. A malicious attacker would also have to constantly watch the mempool for such transactions and there is no guarantee that a transaction with suitable random numbers for the attacker would ever occur.
Manual code inspection. VRF documentation ( https://docs.chain.link/vrf/v2/security#use-requestid-to-match-randomness-requests-with-their-fulfillment-in-order , last accessed on 3/14/2024).
Store the request_id
obtained in request_raffle_winner()
as an internal contract variable and compare it against the input argument when fulfillRandomWords()
is called with an assert statement.
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.