NFT Dealers

First Flight #58
Beginner FriendlyFoundry
100 EXP
Submission Details
Impact: high
Likelihood: high

`buy()` is callable by non-whitelisted users despite whitelist-gated marketplace design

Author Revealed upon completion

Description

Root + Impact

Normal behavior: when protocol policy is whitelist-restricted trading, only approved wallets should buy listed NFTs.

Issue: buy() has no whitelist gate. Any external address can purchase listed NFTs.

Description Explanation

The contract enforces whitelist checks for minting and listing, which suggests a restricted participant model during controlled phases. Omitting the same control on buy() creates an authorization gap where the most sensitive market action (acquiring listed assets) is still public.

If operational assumptions, compliance constraints, or launch mechanics depend on allowlisted participation, this mismatch causes immediate policy bypass.

// @> Missing onlyWhitelisted modifier
function buy(uint256 _listingId) external payable {
Listing memory listing = s_listings[_listingId];
if (!listing.isActive) revert ListingNotActive(_listingId);
require(listing.seller != msg.sender, "Seller cannot buy their own NFT");
...
}

Risk

Likelihood:

  • Public function with no access restriction.

  • Exploitable in every deployment where buyer allowlist is expected.

Impact:

  • Access-control policy mismatch and possible compliance/business-logic violations.

  • Restricted launch assumptions fail immediately.

Proof of Concept

  1. A whitelisted seller creates a valid listing.

  2. A non-whitelisted wallet approves USDC and calls buy().

  3. Purchase succeeds and ownership transfers to non-whitelisted account.

  4. This confirms absence of whitelist enforcement on buying.

// test/NFTDealersFindingsPoC.t.sol
function testPoC_NonWhitelistedWalletCanStillBuy() public {
_mintAndList(sellerA, 1, PRICE_A);
vm.startPrank(nonWhitelistedBuyer);
usdc.approve(address(nftDealers), type(uint256).max);
nftDealers.buy(1);
vm.stopPrank();
assertEq(nftDealers.ownerOf(1), nonWhitelistedBuyer);
}

Recommended Mitigation

Applying onlyWhitelisted to buy() aligns access control with restricted-market assumptions and removes policy ambiguity. If permissionless buy is desired instead, protocol docs should explicitly state that behavior so reviewers and users do not rely on whitelist-based purchase restrictions.

-function buy(uint256 _listingId) external payable {
+function buy(uint256 _listingId) external payable onlyWhitelisted {

If permissionless buying is intentional, document it clearly in README/spec to avoid incorrect security assumptions.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!