The listings IDs are always matching the token ID of the NFT for sale. For example, the NFT #5 will always be sold in the listing #5. This feature leads to an important complexity in some functions. When an NFT is sold through a listing, the payment is sitting inside the contract account. To collect this payment, the seller needs to call the collectUsdcFromSelling function. This function is supposed to check if the caller is the NFT seller through the modifier onlySeller(_listingId). If we look closely at this modifier's code, we can see a problem here:
The modifier checks if the msg.sender is the seller of the listing. But the listing is now inactive and completely obsolete as it has been filled by the buyer. The latter now has full control of the listing and can become the new seller by calling list on the listing/token ID. If the seller did not collect his payment before the buyer lists the NFT back, the buyer will be able to collect the total amount of USDC that he previously paid, resulting in a free NFT for him and permanently lost funds for the seller.
Likelihood:
This will occur whenever the contract's USDC balance is not zero
This will occur after every NFT sale
Impact:
Sellers will lose their payments and collateral
Buyers can get NFTs for free
Buyers can steal sellers' collateral
Actors:
Seller: A normal user minting an NFT and selling it
Attacker: A malicious user stealing the NFT from Seller
Proof of Code:
Listings should have a different indexation than NFTs, but it would imply to change the structure of the code.
A better solution would be to send the payment directly to the seller instead of letting funds sit in the contract account. Make sure to respect CEI to avoid reentrancy vulnerabilities:
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.