← Back to list

!! Emergency Alert!! Analysis of OpenZeppelin Arbitrary Address Spoofing Attack

Summary

ZAN · 2023-12-08 12:47 · 9 claps · 4.0 min read
#attack-analysis #web3
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3 🥊 · Combat Sports

!! Emergency Alert!! Analysis of OpenZeppelin Arbitrary Address Spoofing Attack

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.

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*.

[embed]

Attack Analysis

From OpenZeppelin’s blog, it is evident that several attacks have already occurred. Here we will select one transaction for detailed analysis.

1. Addresses involved in the attack

Attack transaction:

https://etherscan.io/tx/0xecdd111a60debfadc6533de30fb7f55dc5ceed01dfadd30e4a7ebdb416d2f6b6

Attacker address:

0xfde0d1575ed8e06fbf36256bcdfa1f359281455a

Attack contract:

0x6980a47bee930a4584b09ee79ebe46484fbdbdd0

TIME token (Vulnerable contract):

0x4b0e9a7da8bab813efae92a6651019b8bd6c0a29

2. Attack Process

2.1. Attack preparation

  1. The attacker approved the Uniswap V2 Router contract to use his TIME tokens.

  2. The attacker deposited 5 ether into WETH contract and received 5 WETH.

2.2. Attack execution

  1. 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.

  2. The attacker called the execute function of the Forwarder contract, specifying the to address as the TIME contract address and data in the parameter req. Another parameter, signature, was a signature made by the address 0xa16a5f37774309710711a8b4e83b068306b21724 (another address of the attacker), and req.from was also this address, so the signature verification of the execute function could pass smoothly. Next, the function of the TIME contract will be called with the calldata consisting of req.data and req.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.

struct ForwardRequest {
    address from;
    address to;
    uint256 value;
    uint256 gas;
    uint256 nonce;
    bytes data;
}

function execute(ForwardRequest calldata req, bytes calldata signature)
public
payable
returns (bool, bytes memory)
{
  require(verify(req, signature), "MinimalForwarder: signature does not match request");
  _nonces[req.from] = req.nonce + 1;

  // solhint-disable-next-line avoid-low-level-calls
  (bool success, bytes memory result) = req.to.call{ gas: req.gas, value: req.value }(
    abi.encodePacked(req.data, req.from)
  );

  if (!success) {
    // Next 5 lines from https://ethereum.stackexchange.com/a/83577
    if (result.length < 68) revert("Transaction reverted silently");
    assembly {
      result := add(result, 0x04)
    }
    revert(abi.decode(result, (string)));
  }
  // Check gas: https://ronan.eth.link/blog/ethereum-gas-dangers/
  assert(gasleft() > req.gas / 63);
  return (success, result);
}
  1. Observing the transaction, we can see that the function being called at this point is the multicall function of the TIME contract. Within the multicall function, a delegatecall is made to execute a function within the current contract (TIME). In this transaction, the burn function of the TIME contract was called.
function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
    results = new bytes[](data.length);
    for (uint256 i = 0; i < data.length; i++) {
        results[i] = _functionDelegateCall(address(this), data[i]);
    }
    return results;
}

function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
    require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");

    // solhint-disable-next-line avoid-low-level-calls
    (bool success, bytes memory returndata) = target.delegatecall(data);
    return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed");
}
  1. 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, the msg.sender is the Forwarder contract, which is a forwarder trusted by the TIME contract, and therefore isTrustedForwarder 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.
function burn(uint256 amount) public virtual {
    _burn(_msgSender(), amount);
}
function _msgSender() internal view virtual override returns (address sender) {
    if (isTrustedForwarder(msg.sender)) {
        // The assembly code is more direct than the Solidity version using `abi.decode`.
        assembly {
            sender := shr(96, calldataload(sub(calldatasize(), 20)))
        }
    } else {
        return super._msgSender();
    }
}
0x42966c680000000000000000000000000000000000000000c9112ec16d958e8da8180000760dc1e043d99394a10605b2fa08f123d60faf84
  1. 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.

  2. 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.

This article is reprinted from AntChain Open Labs.

View the original article link: https://antchainopenlabs.github.io/2023/11/28/KyberSwap-Attack-Analysis-Unveiling-the-Most-Sophisticated-Cyber-Heist-in-History/

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:*https://zan.top/home*

Twitter | Discord | Telegram | Linkedin


메타데이터
post_id
993ee3cb30eb
slug
emergency-alert-analysis-of-openzeppelin-arbitrary-address-spoofing-attack-993ee3cb30eb
url
https://medium.com/@zan.top/emergency-alert-analysis-of-openzeppelin-arbitrary-address-spoofing-attack-993ee3cb30eb
canonical_url
https://medium.com/@zan.top/emergency-alert-analysis-of-openzeppelin-arbitrary-address-spoofing-attack-993ee3cb30eb
author_url
https://medium.com/@zan.top
status
ok
fetched_at
2026-07-28 05:18:14