By: 九九
background
On December 5, 2023, the Web3 basic development platform thirdweb stated that a security issue was found in the pre-built smart contract, and all ERC20, ERC721, and ERC1155 tokens deployed using the pre-built smart contract were affected. (For specific affected contract code versions, please refer to: https://blog.thirdweb.com/security-vulnerability/)
According to intelligence from the SlowMist security team, on December 7, 2023, the Time token on the ETH mainnet was attacked precisely because of this vulnerability, and the attacker made a profit of approximately US$190,000. There are still many token contracts with vulnerabilities being attacked. The SlowMist security team immediately intervened in the analysis and shared the results as follows:
prerequisite knowledge
1. ERC-2771 is the standard for meta transactions. Users can delegate the execution of transactions to a third-party Forwarder, often called a relay or forwarder.
Usually the address of the direct caller in the contract is obtained using msg.sender, but in the case of using ERC-2771, if msg.sender is the forwarder role, the incoming calldata will be truncated and the last 20 words will be obtained. section as the direct caller address of the transaction.
2. Multicall is a smart contract library that allows multiple function calls to be executed in batches, thereby reducing transaction costs. This library is often used to optimize the performance and user experience of DApps, especially when multiple read operations are required.
As can be seen from the code, the Multicall library used by the vulnerable contract in the thirdweb project executes other functions in the contract that references the library by cyclically calling the DelegateCall function.
root cause
The root cause of the vulnerability is that the token contract uses both the ERC-2771 and Multicall libraries. The attacker calls the multicall function of the token contract through the execute function of the Forwarder contract to execute other functions in the contract (such as burning tokens). This method successfully passes the isTrustedForwarder judgment of ERC-2771, and finally resolves the caller of the function into the last 20 bytes of the malicious calldata. Therefore, the attacker successfully deceived the contract into thinking that the caller was the address of another user, which in turn led to the burning of other users' tokens.
Analysis of attack steps
Here we take the attack transaction 0xecdd11...f6b6 as an example for analysis:
1. The attacker first used 5 WETH to exchange for 345,539,9346 Time tokens in the Uniswap V2 pool.
2. Then call the execute function of the Forwarder contract and construct malicious data to call the multicall function of the token contract. At this time, the token contract will use the malicious data passed in by the attacker to delegateCall to execute the burn function of the token contract and burn the pool address. 62,227,259,510 Time tokens.
3. Since the previous step burned a large number of Time tokens in the pool, causing the price of Time tokens to rise instantly, the attacker can finally reverse swap the Time tokens obtained in the first step, emptying out the pool. 94 WETH.
Analysis of attack principle
In the execute function of the Forward contract, after verifying the signature of req.from, call will be used to interact with req.to (token address). The req.data passed in by the attacker is
Because 0xac9650d8 is the function signature of the multicall function, the multicall function of the token contract will be called, and the data value passed in by the multicall function is 0x42966c6800000000000000000000000000000000000000000c9112ec16d958e8da8180000760dc1 e043d99394a10605b2fa08f123d60faf84.
Why is there no req.from in the data value passed into the multicall function? This is because the EVM bottom layer will truncate the required value based on the offset when processing the call. The offset set in the calldata value passed by the attacker is 38, and the value length is 1, so it just intercepts The data value is 42966c6800000000000000000000000000000000000000c9112ec16d958e8da8180000760dc1e043d99394a10605b2fa08f123d60faf84.
For details, please refer to the description of call in the EVM opcode (https://www.evm.codes/?fork=shanghai).
Since 0x42966c68 is the function signature of the burn function, the burn function of the token contract will be called through delegatecall based on the data value constructed by the attacker.
The _msgSender() function is overridden by the ERC-2771 library.
Since multicall is called through delegatecall, the msg.sender passed in by isTrustedForwarder is actually the address of the Forward contract, thus passing the judgment, and ultimately the value returned by _msgSender() is the last 20 bytes of the calldata passed in. That is, the address of the pool is 0x760dc1e043d99394a10605b2fa08f123d60faf84.
in conclusion
The root cause of this attack is that the contract references both Multicall and ERC2771Context. The attacker can insert malicious calldata in the forwarding request, use the delegatecall function of Multicall to pass the judgment of the trusted forwarder, and manipulate the _msgSender() in the sub-call. Analysis, so that any user’s token can be manipulated.
The SlowMist security team recommends that project parties do not use Multicall and ERC2771Context at the same time when writing token contracts. If the expected demand requires simultaneous reference, you must check whether the calldata length meets expectations or use the latest official version of openzeppelin's Multicall and ERC2771Context contracts.
reference
Attacker address: 0xfde0d1575ed8e06fbf36256bcdfa1f359281455a
Attack contract: 0x6980a47bee930a4584b09ee79ebe46484fbdbdd0
Related attack transactions: https://etherscan.io/tx/0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6
Affected version details: https://blog.thirdweb.com/security-vulnerability/
Mitigation tool: https://mitigate.thirdweb.com/