As part of our ongoing Blockchain and Web3 security research, we investigated the use of different standards of offline signatures in Web3 dapps, and revisited one of the biggest hacks in this domain: The OpenSea-related offline signature phishing attack earlier this year that resulted in the theft of NFTs valued at millions of USD.
This technical blogpost will revisit and explain the original attack, evaluate OpenSea’s upgrades & mitigation for their smart contracts, and demonstrate that OpenSea is still potentially vulnerable to such attacks. We will conclude with some practical recommendations for users.
Key takeaways:
- Offline signatures can still be dangerous in many cases, oftentimes resulting in loss of assets: This includes active OpenSea contracts
- These scams were active on older versions of OpenSea smart contracts
- The updated SeaPort contract is not safe from this kind of scam;
- We have already seen such a type of scam implemented in production.
- Part 2 of this series will introduce an unknown potential attack vector using SeaPort
OpenSea original incident explained: WyvernV1
OpenSea is the leading NFT trading platform with a monthly volume of more than $5B at its peak in January 2022, according to Cointelegraph, shortly before the incident.
In February 2022, a phishing scam broke out. In order to better understand how the scam worked, let’s first breakdown OpenSea’s normal listing process:
Step One:
- The NFT seller first needs to approve the OpenSea contract as an operator for the relevant NFT collection – meaning the OpenSea contract will have the permissions to move NFTs from this collection.
- The destination of this transaction is the NFT collection contract, the called function named SetApprovalForAll and is part of the EIP-721 & EIP-1155 standard, means every ERC721 & ERC1155 contract (NFTs) should have this function on its code.
- The function receives 2 parameters: An address (that can access your token from that specific collection) and a Boolean (which represents the state – true if we want to grant permissions)
Step Two:
Next, the seller is asked to sign an offline message that represents the listing parameters (e.g. price) that they submitted on the OpenSea application UI.
Once the seller signs that message, OpenSea updates the NFT’s status application as available for buying.
Step Three:
When a buyer makes a purchase, they send the listing parameters to the contract along with the listing signature as stored on OS DB. The smart contract then compares the purchase parameters against the seller’s listing parameters and if they are met, the purchase event will go through successfully and the OS contract will move the NFT to the buyer and ETH (or any other token) to the seller.
Since the signature is a derivative of the selling parameters and the seller’s private key, a potential hacker cannot fake a valid signature and by that steal an NFT using the OS contract
To overcome this obstacle, Scammers need to trick the victim to sign on a listing message, with parameters that the scammers chose, most likely selling the victim’s precious NFT for a very low price, or even zero.
To do so, scammers may apply various phishing techniques, leveraging the fact that these message parameters are unclear for most users. When the original phishing scam against OpenSea users took place, it asked the victims to sign a malicious listing message abusing the fact that it’s impossible for the victims to understand what they actually sign:
And that’s what happened in the February scam: the scammers managed to accumulate malicious listing signatures from victims by tricking them into unknowingly listing their precious NFTs for the price of $0. This allowed the scammers to later “buy” all these NFTs at once (for the price of 0), right before the migration to a new contract.
More info can be found here.
The first migration: WyvernV2
OpenSea’s Migration to WyvernV2 in February 2022 was planned before the attack and was probably expedited as a mitigation.The purpose of this migration was to support the EIP-712 signatures standard. EIP-712 allows users a better understanding of the message since the parameters are shown, and users no longer need to sign off on inscrutable hexadecimal strings.
However, while the parameters are indeed visible it is still barely possible for the non expert user to understand their actual meaning.
The latest migration – (SeaPort)
In June 2022, OpenSea migrated from the aforementioned WyvernV2 contract to its current SeaPort contract, which is also the latest implementation.
The main purpose of the migration was to improve the trading experience & allow extra features like: collection offers, more advanced exchange options, and saving gas by using more efficient implementation mechanisms.
More info on SeaPort can be found here and here.
Like WyvernV2, SeaPort also supports EIP-712 signatures as its signing method. Although in terms of signature clarity, SeaPort doesn’t make it easier for a non-expert user to figure out what’s going on. It uses some complex structs in order to represent the listing price and collection fees are part of that structure.
Are we saved? No: Here’s how we reproduced the attack on OpenSea’s newest Smart Contract (SeaPort)
SeaPort’s complex signature struct allows a potential scammer to make an inexperienced user sign a malicious listing through a phishing website, which emphasizes the need of making signatures (and transactions) more transparent for users.
We wanted to see if the attack is still feasible on OpenSea’s latest version. To do so we had to take a dive into OpenSea’s current SeaPort contract
Overall In terms of the listing & buying it’s similar process as described above, but the signature structure was completely changed:
Let’s dig in the critical signature parameters in 3 steps:
Step One:
The listing value is determined by an array called consideration. Each cell of that array is another recipient for the buying transaction. If choosing a regular listing (not an auction), startAmount and endAmount will be the same and are calculated in wei (in a case of ETH listing like in the example)
Step Two:
If for example I chose to list my NFT for 1 ETH
OS will automatically calculate all the consideration values in wei then the signature request will display:
In this example, the first consideration cell represents the value to be transferred to the seller address (the signer), the second cell represents the value to be transferred to OS (which is being generated automatically by OS frontend), and it represents 2.5% of the value.
Since the collection royalties are 0% there are only 2 cells.
Step Three:
When the NFT is purchased and the recovered parameters match the DB parameters:
The order will be fulfilled and the SeaPort contract will move the NFT (since it was approved) from the seller’s wallet to its new owner – the buyer.
These are the Order parameters as being represented on the contract:
More info about the parameters can be found here.
As you can see, consideration is the only input in the signature that determines the listing value. If a scammer makes the seller sign a fraudulent listing (where the consideration has no value) he would be able to take the NFT for free – assuming the SeaPort contract is approved as an operator for transfer for that collection.
Once the scammer has the signature he can send a transaction with the user signature (using for example the ethers.js library).
https://youtu.be/PPdyUl5Qie4
Recommendations
- Users should understand exactly what they sign – in that example it’s important to understand that ‘consideration’ represents the selling value. In most cases, though, we cannot just expect users to understand that signature structure
- Be extra cautious when signing EIP712 signatures that can be used in contracts
- Wallets should give a better understanding for the signature content, and in other cases warn users against malicious signatures – as with Zengo’s ClearSign technology
Want to learn about Part 2?
It’s live! Read it here.
Also, follow Zengo on Twitter for latest updates: @Zengo
Learn more about Zengo X, our open-source MPC library, and github here.