As specified in the README, the protocol should allow sellers collecting its proceeds (listing.price - fees) after selling an NFT.
The normal calling order would be like this
buyer calls buy(listingId) and succeeds. <- this set its listing.isActive to false.
seller calls collectUsdcFromSelling(listingId) and succeeds.
Now an attacker can:
attacker calls buy(listingId) and succeeds
attacker calls list(listingId) <- This call assigns the buyer address to the listing.seller and set the listing.price .
attacker calls cancelListing(listingId) <- This call sets the listing.isActive to false.
attacker calls collectUsdcFromSelling(listingId) <- Now the attacker collects the listing.price - fees from the protocol, given the protocol still hold enough USDC balance for the pay out.
The root cause is that the protocol is using listing.isActive to determine if a NFT transaction has occured in collectUsdcFromSelling()but it is also used to indicate if the listing is active or not and can be turned on or off by list() and cancelListing().
Likelihood:
The attacker can make the above call orders: 1) buy, 2) list, 3) cancelListing, 4) collectUsdcFromSelling to exploit this vulnerability.
Impact:
The attacker can withdraw all the USDC balance of the protocol/smart contract.
The attacker can perform the following series of calls to exploit this vulnerability
Without introducing another state variable, an easy mitigation is to ensure buy() and collectUsdcFromSelling() are being called atomically. They can be seen in the following mitigation code.
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.