It is possible for anyone to claim the airdrop rights incurred by the first couple of soulmates (id 0) by just calling the claim()
function of the contract Airdrop.sol
.
The first two sections highlighted below, and in the "Relevant GitHub Links", show how the claim()
function is making wrong assumptions about the returned values by the soulmateContract
contract.
First, it is assuming that if the caller is not divorced, it does have a soulmate. However, it could be the case where the caller does not have a soulmate hence the reason why isDivorced()
would return false
.
Then, the function is calculating the number of days that the couple has been together to determine the airdrop amount. For that, it populates the local variable numberOfDaysInCouple
with soulmateContract.idToCreationTimestamp(soulmateContract.ownerToId(msg.sender))
. Notice that soulmateContract.ownerToId(msg.sender)
would always return 0 for addresses that do not have a soulmate. Therefore, the numberOfDaysInCouple
local variable would be equal to soulmateContract.idToCreationTimestamp(0)
, so the creation timestamp of the first couple (id 0).
Finally, the flaw can be exploited by multiple addresses until the contract is drained because of the third highlighted part of the code, where the _claimedBy
mapping is used. This allows different accounts to exploit the vulnerability explained above several times as _claimedBy
will always be 0 for new addresses.
An attacker could call the claim()
function of the Airdrop.sol
contract multiple times using different accounts until it is completely empty.
Add the following test to AirdropTest.t.sol
and run it with forge test --mt test_ClaimByAttackers -vvvv
. The test is calling claim()
10 times using different accounts.
Foundry and manual analysis.
It is recommended to implement a proper mechanism to verify that a couple exists (and it is not divorced), that the account claiming the airdrop does indeed have such rights, and that the same period cannot be claimed several times.
High severity, This issue is separated from the flawed `isDivorced()` check presented in issue #168 as even if that is fixed, if ownership is not checked, isDivorced would still default to false and allow bypass to claim airdrops by posing as tokenId 0 in turn resulting in this [important check for token claim is bypassed.](https://github.com/Cyfrin/2024-02-soulmate/blob/b3f9227942ffd5c443ce6bccaa980fea0304c38f/src/Airdrop.sol#L61-L66). #220 is the most comprehensive issue as it correctly recognizes both issues existing within the same function.
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.