At approximately 3:20 PM EST on January 24 2022, the 0x Protocol team reached out privately and directly to disclose a vulnerability in ZORA’s AsksV1 module. Importantly, no user funds have been lost and no users are at immediate risk of losing funds. However, ZORA identified up to 31 users who have the potential to be at risk in the future. This report outlines the vulnerability, the steps we’ve taken to mitigate, and the timeline of events as they unfolded.
ZORA’s AsksV1 module (also referred to as “Buy Now”) allows a user to list any NFT for sale for a fixed price and currency. A potential buyer is then able to fill that listing by calling a method on the AsksV1 contract. When called, the ZORA contract transfers the purchasing funds out of the buyer’s account and sends them to the seller for payment. In return, the ZORA contract then transfers the listed NFT out of the seller’s account to the buyer. The two method signatures are shown below:
/// @notice Creates the ask for a given NFT /// @param _tokenContract The address of the ERC-721 token to be sold /// @param _tokenId The ID of the ERC-721 token to be sold /// @param _askPrice The price to fill the ask /// @param _askCurrency The address of the ERC-20 token required to fill, or address(0) for ETH /// @param _sellerFundsRecipient The address to send funds once the ask is filled /// @param _findersFeeBps The bps of the ask price (post-royalties) to be sent to the referrer of the sale function createAsk( address _tokenContract, uint256 _tokenId, uint256 _askPrice, address _askCurrency, address _sellerFundsRecipient, uint16 _findersFeeBps ) external
/// @notice Fills the ask for a given NFT, transferring the ETH/ERC-20 to the seller and NFT to the buyer /// @param _tokenContract The address of the ERC-721 token /// @param _tokenId The ID of the ERC-721 token /// @param _finder The address of the ask referrer function fillAsk( address _tokenContract, uint256 _tokenId, address _finder ) external payable
The vulnerability lies in a race condition that can happen in the Ethereum mempool. If a user is paying for an NFT in an ERC-20, they must first approve ZORA to pull funds out of their account. A common pattern is for interfaces to automatically approve an infinite amount of a user’s token to be spent by a contract. This allows for a UX improvement of a user never needing to approve a contract more than once per currency, but comes with the security tradeoff of trusting every approved contract to not pull all of a user’s funds out of their account unexpectedly.
Therein lies the problem. When a buyer attempts to fill a listing on AsksV1, a malicious seller has a very small window to try to edit their listing before it is filled. The seller could increase the price to the sum of the buyer’s ERC-20 balance and submit the transaction with a very high gas price. If the listing update was executed on chain prior to the listing being filled, the buyer would unintentionally drain their account to purchase the NFT.
We’ve tested and deployed a new market module, AsksV1.1, to ZORA V3 with an updated function signature as shown below
/// @notice Fills the ask for a given NFT, transferring the ETH/ERC-20 to the seller and NFT to the buyer /// @param _tokenContract The address of the ERC-721 token /// @param _tokenId The ID of the ERC-721 token /// @param _fillCurrency The address of the ERC-20 token using to fill /// @param _fillAmount The amount to fill the ask /// @param _finder The address of the ask referrer function fillAsk( address _tokenContract, uint256 _tokenId, address _fillCurrency, uint256 _fillAmount, address _finder ) external payable
This update effectively mitigates the vulnerability for future users by requiring buyers to explicitly include the amount and currency type they are expecting to pay in. If these parameters do not match the listing, the function reverts.
This updated contract has been deployed to Rinkeby and Mainnet, and is registered with ZORA V3. We’ve also updated zora.co to reference this new contract, and to not show stale listings from the vulnerable AsksV1 contract.
At the time of writing this, up to 30 users are potentially vulnerable. Note that no funds are currently at risk, nor have any funds been exploited so far. However, we have contacted every identifiable user with steps to unapprove the AsksV1 module and ensure they are not subject to this vulnerability in the future. If you believe you may be affected by this, please follow the below steps immediately.
ZORA will reimburse all gas costs associated with these transactions for any users who have been affected by this prior to this notice being published. Please send any relevant transactions to email@example.com.
Step 1: Cancel current “Buy Now” Listing
If your NFT is currently listed with a “buy now” listing you will need to cancel it. If you have only approved your tokens to V3, you will not need to cancel any listings and can continue to Step 2.
Step by step guide on how to cancel your “buy now” listing: https://support.zora.co/en/articles/5882399-how-to-cancel-your-listing
Step 2: Revoke contract approval
Revoking the contract approval can be achieved via Etherscan and by following the steps listed below. If you have any questions, please email firstname.lastname@example.org
Step 3: Approve the Asks V1.1 module
This is the same process you would have gone through when originally listing on zora.co. (link)
Step 4: List your NFT
Finally, you can re-list your NFT with a fixed price (link)
We were very fortunate to have this vulnerability reported by the 0x Protocol team early. In accordance with our bug bounty policy, we have paid out a 10 ETH bug bounty to the 0x Protocol team. In addition, we are implementing a number of changes to our contract deployment process to catch these errors before they are deployed. To start, we’re going to allow bug bounties to be fulfilled in our repository before we deploy our contracts. Any PR marked with a “Community Feedback Needed” label is applicable to our bug bounty policy. This will give our community of Solidity developers and security engineers the opportunity to receive a bug bounty before a vulnerability is deployed to mainnet.
We will be implementing deprecation notices in our documentation and SDK to notify developers of discovered vulnerabilities in any deployed module.
Within zora.co, we are going to create a dashboard for ZORA users to easily manage which modules they have approved on V3. This dashboard should also serve as a notifications hub for any future vulnerabilities that are discovered. The nature of ZORA module approvals allows users to protect themselves from vulnerabilities by removing their approvals from the ZORA module manager, which means this dashboard can act as a simple UI to opt-out of vulnerable modules.
The timeline below outlines the series of events and responses the ZORA team took while handling this incident.
Thank you to our incredible community of developers who are always inspecting the ZORA protocol, and for disclosing vulnerabilities in a collaborative, direct and transparent manner.