On December 8, 2023, OpenZeppelin officially released an important security alert to the community. The alert points out that when using the ERC-2771 standard with Multicall-like methods in project integration, there may be a risk of arbitrary address spoofing attacks.
SharkTeam conducted a technical analysis of this incident immediately and summarized the security precautions. We hope that subsequent projects can learn from this and jointly build a security defense line for the blockchain industry.
1. Attack transaction analysis
Since there are a series of attack transactions related to this vulnerability, we selected one of the attack transactions for analysis.
Attacker address:
0xFDe0d1575Ed8E06FBf36256bcdfA1F359281455A
Attack transaction:
0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6
Attack process:
1. First. The attacker (0xFDe0d157) first used 5 WETH to exchange for approximately 3, 455, 399, 346 TIME.
2. Subsequently, the attacker (0xFDe0d157) constructed a malicious calldata parameter and called the [Forwarder].execute function.
3. When calling the [Forwarder].execute function, the malicious calldata triggered the multicall function of the TIME contract. Subsequently, the remaining calldata is used to trigger the burn function of the TIME contract to destroy the TIME tokens in the pool.
2. Vulnerability analysis
First of all, this attack mainly involves several aspects: ERC 2771, Multicall, and carefully constructed calldata. We can find the relevant inheritance from the TIME token contract:
1. ERC 2771 provides the ability to have a virtual msg.sender, allowing users to entrust a third party [Forwarder] to execute transactions to reduce gas costs. When a transaction is submitted, the msg.sender address is added to the calldata.
2.TIME token contract inherits ERC2771Context. When [Forwarder] calls the contract, _msgSender() checks the calldata data and shifts it right, truncating the last 20 bytes as the expected msg.sender.
3.Multicall is a method that transforms a single function call into multiple functions called sequentially in the same contract. It accepts an array of user-coded calls and executes its own contract. This function iterates through the calls array and performs delegatecall() on each operation. This allows users to combine their own series of operations and execute them sequentially in the same transaction without having to pre-define certain combinations of operations in the protocol. Its main purpose is also to save gas.
4. For the carefully constructed calldata, the attacker called the [Forwarder].execute function and passed in relevant parameters.
We format the data value accordingly in a readable manner and get:
The attacker (0x FDe 0 d 157) obtains the new data value through the offset operation of the current calldata and passes the value to the multicall(bytes[]) function. The first 4 bytes of new data are the selector of the burn(uint 256) function, and the amount parameter is 62227259510000000000000000000.
5. In the multicall(bytes[]) function, call the burn(uint 256) function through delegatecall. In the line 0x 20, the address 0x760dc1e043d99394a10605b2fa08f123d60faf84 is initially added at the end when constructing calldata. This address corresponds to the TIME-ETH liquidity pool on Uniswap v2, which is the expected msg.sender mentioned above.
6. Why did the msg.sender mentioned just now become a TIME-ETH liquidity pool address? The reason is that msg.sender is the [Forwarder] contract address at the beginning. In order to determine whether it is a trusted [Forwarder], if it is a trusted [Forwarder], set msg.sender to the last 20 bytes of calldata.
3. Safety Suggestions
The root cause of this attack: In ERC-2771, [Forwarder] is not designed for multicall. The attacker adds relevant parameters in the _msgSender() function to the external call of multicall, that is, the [Forwarder].execute function of this event. In the multicall function, some functions will also append relevant parameters in _msgSender(), allowing attackers to spoof _msgSender(). Therefore, an attacker can imitate calls to arbitrary addresses by using multicall to call related functions. Finally, the TIME tokens in the pool are destroyed through authorization.
In response to this incident, the following mitigation and prevention measures can be taken:
1. Use the new version after fixing the bug. The new version of Multicall of OpenZeppelin has the context suffix length of ERC 277 1context data, which is used to identify the expected context suffix length of ERC-2771. Therefore, any call from a trusted [Forwarder] will be recognized and adapted to each sub-function call.
The following is a comparison chart between the bug version and the updated version:
2. Forbid any contract to call multicall to prevent [Forwarder] from using it. Taking ThirdWeb as an example, this method is compared with OpenZeppelin’s solution. OpenZeppelin still allows multicall through contracts. The following is a comparison chart of ThirdWebs relevant bug versions and updated versions.
About Us
SharkTeams vision is to secure the Web3 world. The team consists of experienced security professionals and senior researchers from around the world, who are proficient in the underlying theory of blockchain and smart contracts. It provides services including on-chain big data analysis, on-chain risk warning, smart contract audit, crypto asset recovery and other services, and has built an on-chain big data analysis and risk warning platform ChainAegis. The platform supports unlimited levels of in-depth graph analysis and can effectively fight against Advanced Persistent Threat (APT) in the Web3 world. It has established long-term cooperative relationships with key players in various fields of the Web3 ecosystem, such as Polkadot, Moonbeam, polygon, Sui, OKX, imToken, ChainIDE, etc.
Official website:https://www.sharkteam.org
Twitter:https://twitter.com/sharkteamorg