IOP _ ThunderNFT 34949 - [Smart Contract - Critical] Missing proper validation when updating order

Submitted on Sun Sep 01 2024 17:46:18 GMT-0400 (Atlantic Standard Time) by @anatomist for IOP | ThunderNFT

Report ID: #34949

Report type: Smart Contract

Report severity: Critical

Target: https://github.com/ThunderFuel/smart-contracts/tree/main/contracts-v1/thunder_exchange

Impacts:

  • Direct theft of any user NFTs, whether at-rest or in-motion, other than unclaimed royalties

Description

Brief/Intro

Thunder Exchange lacks proper validation during the update_order function for sell-side orders. When transferring assets back to the user, the exchange may transfer an incorrect amount of previously stored assets, allowing an attacker to steal assets from Thunder Exchange.

Vulnerability Details

When placing a sell-side order, Thunder Exchange checks if the user provides the correct asset and amount that match the details claimed in the order. However, when updating a sell-side order, proper validation is missing. The only check performed is _validate_updated_order when calling the strategy's update_order function, which verifies that the maker, collection, token_id, and payment_asset remain the same, but it does not check for the amount.

When canceling the order, Thunder Exchange transfers back the corresponding asset and amount based on the updated order. Therefore, an attacker can exploit this by placing a sell-side order with the minimum asset amount, then updating the order to a higher amount, and finally canceling it to steal the additional assets from Thunder Exchange.

A prerequisite for this attack is that there must be multiple instances of the same asset stored in Thunder Exchange. In Fuel, NFTs differ from those in Ethereum as they are native assets, blurring the boundary between NFTs and fungible tokens (FTs). This ambiguity makes it plausible that users might use this protocol to sell FTs. Furthermore, fractional NFTs exist in Ethereum, so we can't strongly assert that there is only one NFT for each asset (contract, token_id pair). Therefore, this scenario is highly likely to occur.

Impact Details

An attacker can steal assets from Thunder Exchange by placing a sell-side order with a small amount, then updating the order to a higher amount without proper validation, and finally canceling the order to receive the increased amount. This exploit is possible when there are multiple instances of one asset (i.e., not unique) in Thunder Exchange.

References

https://github.com/ThunderFuel/smart-contracts/blob/260c9859e2cd28c188e8f6283469bcf57c9347de/contracts-v1/thunder_exchange/src/main.sw#L124 https://github.com/ThunderFuel/smart-contracts/blob/260c9859e2cd28c188e8f6283469bcf57c9347de/contracts-v1/execution_strategies/strategy_fixed_price_sale/src/main.sw#L413 https://github.com/ThunderFuel/smart-contracts/blob/260c9859e2cd28c188e8f6283469bcf57c9347de/contracts-v1/thunder_exchange/src/main.sw#L161

Proof of concept

Proof of Concept

Prerequisite

To demonstrate the impact, we need to set up two accounts: an admin account to set up the Thunder Exchange contract and an attacker account to exploit the vulnerability and steal funds from Thunder Exchange.

Attacker Account

  • Account: 0xd0f45dd4e1722b83b57f9845956cb45a92e8558e6cb9e77a1b28972ad0b87e6c

  • Private Key: 306a75f0093834948e363ece5ba1b5a7eaad99f2fc9ab976ba01c2dbea3320f6

Admin Account

  • Account: 0x400a9edbd439d107dec7273932d2e1df7b7fb49a53eb75323e2d696e8b88aeb6

  • Private Key: a710552d8d29a0f7aefcb412399fbb041a31ae155cb0eb95d370bf8f99f0409f

Local Node Setup

This PoC uses commit a9e5e89f5fb38e3b3d6e6bdfe1ad339a01f2f3b9 of Fuel-Core.

Modify state_config.json to allocate initial funds to the above two accounts:

After building the local node with cargo build --release, run the node with:

Client Patch

This PoC uses commit efda0397c7bee77de73bd726ec0b732d57614973 of Sway.

I used the forc-run client to deploy the contract and run the PoC script. Since adding an output address directly isn't straightforward, I patched the client as follows:

Deploying Thunder Exchange Contract

Use the admin account to deploy all the contracts listed below and retrieve their respective contract addresses. Here's an example output for subsequent reference:

Run the following command in each contract's directory to deploy the contracts:

Example output showing deployed contract addresses:

Setup Script

This script sets up the Thunder Exchange protocol using the admin's account and transfers 100,000 base assets into Thunder Exchange. This setup is to demo the exploit where the attacker can steal these base assets.

Note that the deployed contract addresses are hardcoded in the main function; you will need to change these to your own deployed contract addresses.

Forc.toml

src/main.sw

Running Setup Script

Use the following command with the patched forc-run and the admin account's private key to execute the setup script.

Note that we are using the contract addresses from the deployment output above.

Upon running the setup script, you should see an output similar to the following:

Attacker Script

This script demonstrates the process by which an attacker can place an order of 1 base asset, change the amount to 2, and then cancel the order to get 2 base assets back, effectively stealing 1 extra base asset from Thunder Exchange.

The collection (0x7e2becd64cd598da59b4d1064b711661898656c6b1f4918a787156b8965dc83c) and token_id (0) correspond to the default base asset (f8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07).

Note that the deployed contract addresses are hardcoded in the main function; you will need to change these to your own deployed contract addresses.

Forc.toml

src/main.sw

Running Attacker Script

Before running the attacker script, we need to patch contracts-v1/libraries/src/order_types.sw. Making ExtraParams public will simplify our exploit script.

Use the following command with the patched forc-run and the attacker account's private key to execute the attack script.

Note that we are using the contract addresses from the deployment output above.

Upon running the attack script, you should see an output similar to the following:

From the output above, in the last two logs, we can see that the balance of Thunder Exchange before the attack was 0x00000000000186a0 (100,000), and after the attack, it became 0x000000000001869f (99,999). This confirms that the attacker successfully stole 1 extra base asset from Thunder Exchange.

Last updated

Was this helpful?