Summary
On December 8, 2023, OpenZeppelin issued an important security alert to the community via Twitter, stating that the integration of the ERC-2771 standard with a multicall-like method (wherein the code contains a delegatecall to the contract itself with calldata that can be externally controlled by users) could lead to projects using this pattern being at risk of arbitrary address spoofing attacks. (https://twitter.com/openzeppelin/status/1732913331265036475?s=46&t=zDQbmeyWt2t9a8SGpOvbtw)
OpenZeppelin provided methods for identifying vulnerable contracts and mitigating the issue on their blog. For more details, please read the blog post: https://blog.openzeppelin.com/arbitrary-address-spoofing-vulnerability-erc2771context-multicall-public-disclosure.
Attack Analysis
From OpenZeppelin’s blog, it is evident that several attacks have already occurred. Here we will select one transaction for detailed analysis.
Addresses involved in the attack
Attack transaction:
https://etherscan.io/tx/0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6
Attacker address:
0xfde0d1575ed8e06fbf36256bcdfa1f359281455a
Attack contract:
0x6980a47bee930a4584b09ee79ebe46484fbdbdd0
TIME token (Vulnerable contract):
0x4b0e9a7da8bab813efae92a6651019b8bd6c0a29
Attack Process
Attack preparation
The attacker approved the Uniswap V2 Router contract to use his TIME tokens.
The attacker deposited 5 ether into WETH contract and received 5 WETH.
Attack execution
The attacker called the
swapExactTokensForTokensSupportingFeeOnTransferTokens
function of the Uniswap V2 Router contract, intending to exchange 5 WETH for TIME token. This swap was conducted in the Uniswap V2: TIME 40 pool and finnally the attacker exchanged for 3455399346.269046 TIME tokens.The attacker called the
execute
function of the Forwarder contract, specifying theto
address as the TIME contract address anddata
in the parameterreq
. Another parameter,signature
, was a signature made by the address 0xa16a5f37774309710711a8b4e83b068306b21724 (another address of the attacker), andreq.from
was also this address, so the signature verification of theexecute
function could pass smoothly. Next, the function of the TIME contract will be called with the calldata consisting ofreq.data
andreq.from
. It’s important to note that the calldata was carefully constructed by the attacker and included the address of the Uniswap V2: TIME 40 pool. This pool address was where the funds were lost in the subsequent attack.
1 | struct ForwardRequest { |
- Observing the transaction, we can see that the function being called at this point is the
multicall
function of the TIME contract. Within themulticall
function, adelegatecall
is made to execute a function within the current contract (TIME). In this transaction, theburn
function of the TIME contract was called.
1 | function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { |
- When the
burn
function is called, the address for the token to be burned is determined via the_msgSender
function. Upon entering the_msgSender
function, in this transaction, themsg.sender
is the Forwarder contract, which is a forwarder trusted by the TIME contract, and thereforeisTrustedForwarder
is true. An address is extracted from the last 20 bytes of the calldata to serve as the sender. The calldata at this point is as follows. The last 20 bytes of the calldata here correspond exactly to the address of the Uniswap V2: TIME 40 pool. Ultimately, 62227259510 TIME tokens from the Uniswap V2: TIME 40 pool were burned.
1 | function burn(uint256 amount) public virtual { |
1 | function _msgSender() internal view virtual override returns (address sender) { |
1 | 0x42966c680000000000000000000000000000000000000000c9112ec16d958e8da8180000760dc1e043d99394a10605b2fa08f123d60faf84 |
The attacker called the
sync
function of the Uniswap V2: TIME 40 pool, updating the reserves of tokens to match the actual number of tokens in the pool. After the update, the reserve of TIME tokens in the pool decreased, making the price of TIME in the pool more expensive.The attacker conducted another exchange in the Uniswap V2: TIME 40 pool, this time swapping TIME tokens for WETH. As the price of TIME tokens had increased, the attacker was able to exchange for a larger amount of WETH. Ultimately, using the 3455399346.269046 TIME acquired from the first exchange, the attacker swapped for approximately 94 WETH, making a profit of over 80 ETH.
Recommendation
It is recommended that all project teams promptly utilize OpenZeppelin’s tools (https://defender.openzeppelin.com/v2/#/auth/sign-up) to check if their contracts are vulnerable to the aforementioned exploit and take immediate actions such as pausing the project and revoking trust from forwarders , etc., to mitigate this risk.
About
AntChain Open Labs
AntChain Open Labs is a research center initiated by AntChain and world leading computer scientists in the area of foundational trust technologies. It is dedicated to building a secure, transparent and reliable Web3 infrastructure driven by innovative research and aiming to advance transformative services.
Website:https://openlabs-intl.antdigital.com/home
ZAN
ZAN, powered by AntChain Open Labs, provides solutions for Web3, such as Smart Contract Review, KYT, KYC, Node Service, and more.
Website | Telegram | Discocd | Twitter | More