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
a9e5e89f5fb38e3b3d6e6bdfe1ad339a01f2f3b9of 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
efda0397c7bee77de73bd726ec0b732d57614973of 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) andtoken_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?