August 8th incident

🔔
As of August 10, all affected users have been fully reimbursed

Incident timeline

  1. On Aug 08, 2022 04:31:07 PM +UTC, hacker deployed a malicious contract (https://etherscan.io/tx/0x09d859ccad3b3e65e38f0c5816430775b9760cb8e22d29a6ab31a770869598ff) to perform the attack.
  2. On Aug 08, 2022 04:38:12 PM +UTC, hacker executed first transaction to steal funds from a user wallet (https://etherscan.io/tx/0x58a5bf41e11555a8c0faab5fcd6cadc1e90c743da09f1496c0c56bac71bce520)
  3. On Aug 08, 2022 04:57 PM +UTC first message from the community that a possible attack is taking place.
  4. On Aug 8, 2022 05:01 PM +UTC team reacted, within 4 minutes of the notification.
  5. On Aug 08, 2022 05:14:00 PM +UTC last malicious transaction was on executed to drain available funds using the exact same pattern.
  6. On Aug 08, 2022 05:17:15 PM +UTC transfer proxy swap functions were shut down by the team (https://etherscan.io/tx/0xdb941e4343a39ca4735ab17a938ce86c7dbe39ad7f49d836a0a8877ecdc0537a) that should’ve prevented attack vector. We have identified the potential preliminary cause within 16 minutes of initial notification.
  7. After that hacker EOA swapped and sold tokens, then moved the funds to Tornado Cash.
  8. On Aug 08, 2022 08:13:47 PM +UTC After further checks, Mover Transfer Proxy contract was upgraded (https://etherscan.io/tx/0x4356a7c5df27261167293bfadb644ede371ed824c56875e5964d44f96ad93949) to restrict card top ups to only direct ones from sender address, that should cover rare corner cases (to waste ERC20 tokens that allow transfer to zero address, although does not have direct economical rationale) and after further investigation to resume operations.

Attack vector

The separate functions (swap and top-up) were not affected separately, it was the combination of the two that resulted in the ability to obtain funds.

Let’s look at the simplified swap process first.

image

User gives permission to access his/her ERC20 tokens (called allowance) by another address. In our scenario the address is contract Mover Transfer Proxy (0x1ef7a557cfa8436ee08790e3f2b190b8937fda0e). This transfer proxy does not hold any user funds, and is implemented as an upgradeable contract (to allow fixes and functionality updates).

When SwapAsset() method is called, inside TransferProxy a SwapAssetDirect method is called, which is moving token from user wallet directly not to itself, but to exchange proxy (this saves gas for user equalling 1 ERC20 transfer), the exchange proxy is Mover’s and considered a trusted address for transfer proxy contract. Exchange proxy is provided with arbitrary bytes parameter, that should contain an encoded information of what exchanger to call and any parameter it may require (slippage, etc.). This is formed by an application. The rest of the flow is to call the address with data provided, perform a check that the minimal expected amount is met, return the target asset to the user wallet and finish the transaction with ‘swap’ event.

This works fine as it is restricted to user swapping only funds from his/her wallet directly, without being able to swap someone else’s funds. Such function exists within Mover’s Transfer Proxy contract, but its execution is restricted only to appropriate role (for gasless transactions).

If a malicious contract is created to mimic an exchanger contract (it’s address is 0x1aec6d43285e014232d400e8fd722180a164622b) and appropriate data is formed in the call to TransferProxy’s SwapAsset() – In such scenario, it can only impact the caller’s EOA balances (so only own balance could be accessed).

On the other side, TransferProxy has a CardTopup method, which allows user to top-up the card with not only USDC token (which is not being converted at this transaction), but with any swappable ERC20 token. The topup method uses SwapAssetDirect method internally, and that, in combination with the ability to call topup for another address as a parameter, along with malicious contract, allowed to trick TransferProxy that exchange is successful, and pass absolute minimal check about the balance, making a topup for 0.00001 USDC while malicious exchanger contract transferred funds to hacker’s wallet.

image

The feature of topping up card for different addresses was prepared for an upcoming new feature release: ability to top up card from multiple addresses. There is no market leader, in fact, there is no one player on the market that can do that right now.

The full list of affected addresses is here:

Affected Addresses.csv12.8KB

What worked well

This was an isolated incident that affected less than 4.4% of the users, and even a small percentage of the funds (we can only estimate the potential losses should the security checks have not been present). Partially due to the quick response time, and partially due to the technical precautions that were in place.

  • Response time has been 15 minutes. Considering the size of the team, this has been possible due to very specific internal practices: what actions to take (on-chain or off-chain), and how to react/find the right person on the team.
  • Prepared emergency contagion plan. To stop the vulnerability we had to identify it, prepare a quick solution and deploy it on-chain (7 chains to be precise). It wouldn’t be possible to contain the vulnerability should the plan was not in place.
  • Architecture of a Transfer proxy contract. As the main contract that users interact with it naturally confronts more potential attack vectors.

Action items

What are we doing and what we will do to prevent from further attacks on the complicated feature of cross-chain 3rd party top ups.

  • New rules are formulated to be checked strictly against when developing Mover next contracts (or updates):
    • All allowance-having contracts public functions only use msg.sender in any transfers, never something else. The only exception caller is trusted executor EOA role (for gasless transactions);
    • All .call methods without any exception would use a whitelist registry of trusted contracts (swap partners, bridge partners) that would be created as a separate on-chain contract and maintained by the team to not allow malicious addresses to be called from any Mover contract method.
  • Create a static code check contract analyzer that would check all open ends for .call / public / payable before every contract change / update with specific heuristics mentioned above;
  • Notification system will be upgraded to allow to monitor for more abnormal activities.