Description: collectUsdcFromSelling() only checks !listing.isActive, but isActive = false is set both when an NFT is sold (buy()) and when a listing is cancelled (cancelListing()). There is no field tracking whether a sale actually occurred. A seller can cancel their listing and then call collectUsdcFromSelling() to collect listing.price - fees USDC that was never deposited by any buyer.
Impact: Any seller can drain the contract's USDC by listing at a high price, cancelling, and then calling collectUsdcFromSelling(). Other users' collateral and sale proceeds are at risk.
Proof of Concept:
Victims mint NFTs depositing collateral into the contract. The attacker mints one NFT, lists at an inflated price, cancels (recovering their own collateral via the C-1 bug), then calls collectUsdcFromSelling() to drain listing.price - fees from the contract — funds that were never paid by any buyer.
Run forge test --match-test test_poc_C2 -vvv to see the following output:
The attacker started with 20 USDC, recovered it via cancel, then stole 99 USDC from victims' collateral — net profit of 99 USDC with no buyer ever involved. The contract went from 100 USDC to 1 USDC (only fees remain).
Recommended Mitigation: Add an isSold boolean field to the Listing struct. Set it to true only in buy(). Require listing.isSold == true in collectUsdcFromSelling().
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.