Top 3 Bugs from the ThunderNFT Invite Only Program

Top 3 Bugs from the ThunderNFT Invite Only Program

From 12th August to 2nd September 2024, the ThunderNFT protocol hosted an Invite-Only Program (IOP) on the Immunefi platform for researchers to hunt on code for their unique NFT marketplace on Fuel, an L2 on Ethereum that utilizes Fuel VM.

An IOP is a form of Audit Competition which is exclusively accessible to a select group of security researchers, and in this case, they consist of researchers who have submitted at least 1 valid report during the Fuel Attackathon event.

A total of 12 researchers were invited to compete, and 41 valid reports were rewarded from a pool of $65,000 USDC.

Here are the top three findings from the Shardeum Ancillaries audit competition, as identified by the Immunefi team. As of this publication, all vulnerabilities have been FIXED.

1. Insufficient validation in order modifications leading to the theft of NFTs — 34980

The contract includes a functionality that allows users to update their orders from the buying side to the selling side using the update_order(…) function. However, a vulnerability exists in the update_order(…) function due to insufficient validation of NFT ownership.

It is important to note that switching sides is permitted within _validate_maker_order_input.

However, critically, there is no validation to ensure that the attacker actually owns the NFT when the order side is changed. This flaw enables a user to set a sell order for an arbitrary NFT without properly verifying ownership.

On cancellation, the NFT asset is transferred back to the maker, only this time, the maker was never the original owner of the NFT.

An attacker can exploit this by calling the cancel_order(…) function. During the cancellation process, the contract relies on the strategy contract to return the correct order information without performing additional ownership verification. This allows the attacker to transfer an arbitrary NFT to themselves, resulting in the theft of the NFT.

PoC and Exploit Steps:

  1. Create buy orders for valuable NFTs they do not own.
  2. Convert these buy orders into sell orders without actually owning the NFTs.
  3. Cancel these “sell” orders, potentially gaining possession of NFTs they never owned.

Here is a test case that showcases the above exploit.

2. Hardcoded Logic in Execution Result Library Leads to Maker Losses — 34534

The project allows makers to specify the desired quantity of an assetId through the MakerOrder struct as shown below.

However, when an order is fulfilled, the maker receives only a single unit of the assetId regardless of the specified quantity. Despite this, they are charged the full price for the order due to a hardcoded value in the contract.

When a taker fulfills a maker’s order, the _execute_sell_taker_order function verifies that the taker has sent the required amount of tokens as part of the transaction call:

Currently, execution_result.amount is hardcoded to 1, meaning the maker always receives only 1 unit of the specified tokenId, regardless of the quantity requested. This mismatch results in unexpected losses for the maker.

For ERC1155 tokens, the maker always receives only 1 unit of the specified tokenId, regardless of the requested quantity, resulting in unexpected losses.

PoC and Exploit steps:

  1. Deploy and initialize all project contracts, ensuring proper references between them.
  2. Deploy a simple ERC1155 contract that allows arbitrary token minting.
  3. The taker (seller) mints 10 tokens for themselves.
  4. The maker (buyer) places an order for 10 units of the same AssetId minted by the taker.
  5. The taker fills the order but transfers only 1 unit (the transaction fails if more units are transferred).
  6. Verify that the maker received only 1 unit of the ERC1155 token instead of the requested 10 units.

Here is a test case that showcases the above exploit.

3. Flawed Logic in ThunderExchange: NFT Owners with Sell Order Blocked from Accepting Bids — 34714

The _execute_sell_taker_order function validates msg_asset() when the user calls the execute_order function, making it impossible for the NFT seller to accept any bids.

This is because the place_order function requires the seller to transfer the NFT to the Thunder Exchange, leaving the seller without the asset_id (the NFT).

To list an NFT, the owner must call the place_order function with Side == Sell.

When the seller decides to accept a bid, they need to call the execute_order function with Side == Sell.

However, the call to execute_order fails because the user is required to use the transfer function in Sway. This built-in function includes critical checks enforced by the FuelVM, one of which ensures that the caller possesses the specified asset_id and the required amount.

This makes it impossible for the seller to accept any bids; they must either wait for someone to buy the NFT directly or cancel the order.

As a result, sellers are unable to accept bid offers due to incorrect logic in the _execute_sell_taker_order function, which causes the transaction to revert when a bid is accepted.

PoC and Exploit Steps:

  1. Initialize all necessary contracts and configure them, including adding strategies and assets.
  2. The NFT owner places a sell order, transferring their NFT to the ThunderExchange contract.
  3. A bidder places a buy order targeting the same NFT but at a lower price.
  4. The NFT owner attempts to accept the buy order, but the execute_order function fails as the NFT is no longer in their possession.
  5. The PoC highlights a logic flaw that prevents NFT owners from accepting bids while their NFT is listed for sale.

For more interesting bugs and writeups about web3 security, do check out our Medium as well as follow our X/Twitter to get daily updates!