OpenZeppelin Blog

Arbitrary Address Spoofing Attack: ERC2771Context Multicall Public Disclosure

Written by OpenZeppelin | December 7, 2023
On Monday, December 4th, 2023 at 9 pm UTC, OpenZeppelin received a security disclosure from the thirdweb team. It was about the presence of an issue arising from a problematic integration of two specific standards: ERC-2771 and Multicall.
 

The OpenZeppelin team promptly validated that the issue is not particular to the implementations contained in the OpenZeppelin Contracts library.

Nonetheless, in order to protect the ecosystem from this vulnerability, the OpenZeppelin Incident Response team led the effort to identify potentially vulnerable contracts. After the identification phase, the OpenZeppelin team reached out to the wider ecosystem partners to expand the identification efforts and mitigation.

Current Situation

This is a broad issue caused by a problematic integration pattern. It has a wide-reaching impact on the ecosystem, notably for thirdweb users and thirdweb forks. The impacted thirdweb contracts are listed here, but there may be other cases of this problematic integration with contracts from our library or other implementations that may also be affected.

Our team has helped several pools with substantial assets, including this one, by coordinating a systemized reach-out strategy. Nonetheless, with the help of monitoring and analysis from the Dedaub and Ironblocks teams, we are observing some attacks taking place in the wild:

Amount: 84.59 ETH
https://explorer.phalcon.xyz/tx/eth/0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6

Amount: 17,394 USDC
https://polygonscan.com/tx/0x1b0e27f10542996ab2046bc5fb47297bcb1915df5ca79d7f81ccacc83e5fe5e4

Amount: 1.06 ETH
https://explorer.phalcon.xyz/tx/eth/0x4ed1ec3d33c297560ed8f5a782b54d2c52adb20155c543fb64ba9065e45c046c?line=6

Amount: 0.73 ETH
https://explorer.phalcon.xyz/tx/eth/0xc9c7b2ae3a6fb82aec113a328d53bfdd7ddd449e2f76da85d169321df20cd22a?line=6

Amount: 0.36 ETH
https://explorer.phalcon.xyz/tx/eth/0xb5649546e4775e2453d5dd4ded898690fe860154474f92c2fdef4a5770dec025?line=6

Amount: 0.29 ETH https://explorer.phalcon.xyz/tx/eth/0x913f68babb2851903df7efe6bbc7c83dbbd1c2a858df0d47a1328750f90305fb

Overview

Any contract implementing both Multicall and ERC-2771 is vulnerable to address spoofing. In the context of the OpenZeppelin contracts library, this can be done with Multicall and ERC2771Context. An attacker can wrap malicious calldata within a forwarded request and use Multicall's delegatecall feature to manipulate the _msgSender() resolution in the subcalls.

Description

ERC-2771 is a standard for meta transactions. It standardizes how the caller address should be resolved for calls that are relayed by a trusted forwarder. During such a call, msg.sender is the address of the forwarder, while the actual caller is only known (and verified using a signature) by the forwarder itself. As such, ERC-2771 details how this information about the original caller is passed by the forwarder to the contract being called.

As seen in the OpenZeppelin Contracts ERC2771Context implementation, the ERC-2771 standard overrides the msgSender() and msgData() so that calls originating from a trusted forwarder are properly considered. When such a call is detected, the actual caller’s address is extracted from the last 20 bytes of the calldata.

The Attack Visualized

đź’ˇ This is a simple representation of the attack. An attacker can potentially wrap multiple spoofed calls within a single multicall(bytes[])

ReferenceIdentifying the Vulnerability

Contracts integrating ERC2771Context and Multicall from OpenZeppelin Contracts can be detected through OpenZeppelin Defender’s Code Inspector. The OpenZeppelin Defender security platform requires that each of the following conditions have been met before flagging a contract as being vulnerable:

  • The contract imports any contracts with the word Context suffixed in their titles or file names. Any variations are assumed to follow the ERC-2771 pattern.
    • The contract imports any contracts with the word Multicall prefixed in their titles or file names.

A contract is affected if it includes all of the following characteristics:

  • The ability to make a delegatecall to itself with user provided data (e.g., Multicall)
  • Some of the context details (such as the address of the caller) are extracted from the data passed by a trusted contract. In particular, this is the case of ERC2771Context or similar ERC-2771 implementations (including GSNRecipient).
  • An enabled trusted forwarder, or the ability to create one. Note that the ability to perform a successful attack is limited to those addresses who can call the forwarder.

Mitigation Steps

The recommended mitigation steps depend on the instance details. Before taking action, evaluate if the vulnerability has spread to functions with access to other, critical functions:

  • Check access control management functions using _msgSender(). Some concrete examples are onlyOwner or onlyRole functions that make use of _msgSender().
  • Look for common transfer functions such as safeTransferFrom() or transfer().

Step 1. Disable every trusted forwarder (if possible)

Some custom ERC2771Context implementations allow setting a trusted forwarder. Doing so can prevent any gasless transaction from being executed, limiting any possible exploit. It may not be a satisfactory long-term solution, but it is a temporary mitigation until a better solution is found.

Users who can perform this step may skip Step 2.

Step 2

2.1 Pause your contract (if possible)

Pausing your contract will affect your users, but can reduce the risk of the vulnerability. Yet, depending on your implementation, the vulnerability may allow an attacker to unpause your contract. This mitigation, albeit incomplete, adds a small obstacle to the potential attacker, buying some time to complete the next steps.

2.2 Ask your users to remove any approval to your contracts (if relevant)

While the vulnerability may remain exploitable in your contract, make sure to point your affected users to tools like Revoke.cash. This is an integral step, since it can significantly reduce the profitability of the attack in many cases.

Step 3. Prepare an upgrade (if possible)

If your contract cannot be upgraded, move to Step 4.

This potential fix requires keeping only one of either the Multicall or ERC2771Context contracts wherever they happen to both be present. Depending of how your users interact with your contract, one might have more impact on usability of your system than the other, which may help you determine which contract to keep in order to minimize the impact to end-users.

If you use a forwarder that includes some batching mechanism, you may want to remove Multicall in favor of that ERC-2771-based batching solution. This will have an effect on what your users have to sign (EIP-712 typed data + relaying transaction).

You can use the OpenZeppelin Upgrades plugin to check the compatibility of your upgrade operation or coordinate your upgrade with OpenZeppelin Defender. Once the upgrade is done, the vulnerability should be patched. If you have completed this step, you may disregard Step 4.

Step 4. Evaluate snapshot options

Vulnerable contracts that cannot be upgraded do not have a way to restore themselves to a fully working and safe status unless you were able to execute Step 1. However, only performing Step 1 will leave your contracts without support for meta transactions. You may want to plan a migration if you need to re-enable this feature for your system to be operational.

We advise projects to identify any role that the trusted forwarder may have been granted. This is because the forwarder can still be used by an attacker even after Step 1 has been completed. The OpenZeppelin Defender Access Control module can help identify access control roles in contracts of interest.

Please note that migration is a complex process with consequences for your users and other protocols you interact with. However, doing it may be necessary to successfully mitigate the vulnerability. As such, we advise you to reach out to a trusted advisor for planning a migration.

An alternative to upgrading is to take a snapshot of your contract state. Doing so will depend on the way data is stored in your contract, although there are solutions available for ERC-20 and ERC-721 contracts. Other, less standard contracts may require custom logic in order to implement support for a snapshot mechanism. This copy of the state can then be replicated upon deploying the new patched version of the system.

The blockchain keeps track of all changes publicly. Thus, once you have identified a “safe” block number (before any attack was performed), you should be able to build the snapshot in a few days, weeks, or even months. You’ll need time to plan how to use this snapshot as the base of a new deployment.

Keep in mind that any asset held by your contract (as collateral) may not be transferable. Also, if your contract is an asset that is used as collateral/liquidity in a third-party protocol (e.g., it is an ERC-20 traded in an AMM), you’ll have to consider the effect of that migration on the liquidity providers.

The Solution

Keeping our commitment with a secure ecosystem, we released a new update to OpenZeppelin Contracts for both of the 4.x and 5.x versions, allowing the use of Multicall together with ERC2771Context.

While the integration between these patterns remains problematic without the proper measures, the updates made to the OpenZeppelin Contracts library allow its integration in a safe and backwards compatible way.The new version of Multicall comes with a context suffix length for the ERC2771context data, which is used to identify the expected suffix length of ERC-2771. In this way, any call from a trusted forwarder will be identified and adapted to every subcall.

The usage of Multicall now advises to take into consideration that any expectations on the received msg.data could be bypassed by wrapping a call in Multicall, clarifying its potential dangers.

 

Your use of any OpenZeppelin Services, including any information contained herein, is governed by our Terms of Service which are available at https://openzeppelin.com. For clarity, this information, including any potential mitigation steps, are provided solely for informational purposes and you acknowledge that there are risks associated with such information, including that such information may not be accurate or applicable to your circumstances. You understand and agree that the information contained herein is offered on a purely non-reliance basis and any use is at your own risk.