Web3Camp 3P Token Exploit Analysis w/ OpenZepplin’s Arbitrary Address Spoofing Attack

Ancilia, Inc.
3 min readDec 8, 2023

Dec 7, 2023, J Liu

Attackers and Victims

Attackers

0x340509fee1005cce6ec075c53f7a7b2c7b769f9d

0x0000000000000f25a072efa232d8efc0b5ce2436

Victims

0xb4E8Cb86324a9640Af81b48F708f933cB7D12Ac3

Web3Camp: Deployer trusted account

Tx

0x2cd19f82d81ded614f69c48d2680e251eb1cc12756e2275a6a9b32d4ef0e0ae2

Hack TX, transferred a big volume 3p tokens from address 0xb4e8

Details

Web3camp 3P token was hacked. One of the protocol owner’s accounts(0xb4e8), who held 8,500,000,000,000 (1e18) tokens from the beginning, now has about 1000 (x1e18) tokens left. Hacker has transferred ​​ 0x5467373a6afc661b2e758d87b1 which is 6,687,000,000,000(1e18) Hacker now just needs to find swaps to cash it out.

Root Cause

There is an issue with OpenZepplin’s ERC2771Context and MulticallUpgradeable when they are used together. An ABI decoding issue bypassed the forwarder’s signature check and caused a privileges escalation issue. Attackers could do anything under a trusted forwarder’s context. E.G., mint, and transfer tokens.

In contract MinimalForwarder, caller’s address and data will be concatenated together through abi.encodePacked():

For example, if req.data is “a9059cbb000000000000000000000000000000000000c35e4364deffa9059dbadaefd4f8000000000000000000000000000000000000005467373a6afc661b2e758d87b1b4e8cb86324a9640af81b48f708f933cb7d12ac3”

and req.from is “0db0f7dee9c985de5336cf166c2b0261cf934fae”, then it could concatenate to

“00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000058a9059cbb000000000000000000000000000000000000c35e4364deffa9059dbadaefd4f8000000000000000000000000000000000000005467373a6afc661b2e758d87b1b4e8cb86324a9640af81b48f708f933cb7d12ac300000000000000000db0f7dee9c985de5336cf166c2b0261cf934fae”.

Please noted:

  • The green data “00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000058” represent the “bytes” data structure(offset + length)
  • The red 0 is because of the 32 bytes padding.

And when it passes to an arbitrary function selector which is supplied by the attacker, it will pass in the whole data just like that.

The problem is the muticall() function. It decodes the data by following the ‘bytes’ abi data structure which still thinks the pass in data is the original req.data:

00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000058a9059cbb000000000000000000000000000000000000c35e4364deffa9059dbadaefd4f8000000000000000000000000000000000000005467373a6afc661b2e758d87b1b4e8cb86324a9640af81b48f708f933cb7d12ac3

The concatenated address will be discarded when it passes to function _functionDelegateCall(). But in contract ERC2771ContextUpgradeable, there is modifier to get the _msgSender():

So contract ERC2771ContextUpgradeable assumes the pass-in data has the original msg.sender from the forwarder who did the signature check, but it actually was discarded in multicall() because of the abi decoding. Now contract ERC2771ContextUpgradeable took the last 20 bytes of input data which is from the hacker, as the msg.sender and it could be any arbitrary number.

Updates

  • Openzepplin issued an announcement here:

https://blog.openzeppelin.com/arbitrary-address-spoofing-vulnerability-erc2771context-multicall-public-disclosure

--

--

Ancilia, Inc.

Ancilia provide cybersecurity solution to Web3 ecosystem.