AnySwap permit exploit

Let’s take a look on very interesting vulnerability that actually happened quite a while, but the quality is still astonishing till our days.

Part #1 - Vulnerable function

First and foremost goal of the “AnySwap“ was to allow users to swap between any two chains freely. It reduces fees and makes it easier to move between chains. It worked in such way:

The router accomplishes this by wrapping the original token with an "anyToken." For instance, DAI is wrapped as anyDAI, making DAI the underlying asset of anyDAI. This wrapped token is utilized for internal accounting within Multichain. When a user "transfers" DAI from Ethereum to BSC, anyDAI is added to the Multichain anyDAI contract on BSC and burned (subtracted) from the anyDAI contract on Ethereum.

The previously mentioned exploited function, anySwapOutUnderlyingWithPermit, facilitates the swapping of an underlying token using the ERC20 permit() function. This function allows users to authorize a contract to spend their funds by providing a signed approval transaction without needing to submit it directly to the blockchain, thereby reducing the user's gas costs. The signed transaction is represented using the parameters (v, r, s).

So, if we divide the logic by peaces, we would have the following:

  1. First line extracts the address of the underlying token (e.g., "DAI") from its wrapped version (e.g., "anyDAI")

  2. Call the permit on the underlying token to approve to the router

  3. Router safeTransferFrom the token from the user.

  4. The remainder of the function handles the accounting processes associated with the wrapped version of the token and manages its transfer across different chains

Part #2 - Actual exploit

It worth to mention, that this function was used only once - during actual exploit. It means that contract had some other entry-point for the users to interact with the protocol.

Now, let’s take a look on the inputs provided by the attacker and try to analyse what has happened exactly.

  • from is victim address

  • token is attackers’ deployed contract

  • to is destination address (attacker wallet)

  • amount is the value to drain from the respective user

  • deadline is time till the permit is valid

  • v,r,s are all zeros

Eventually, how did attacker managed to steal funds? Here is the most interesting part.

Attacker provided the token, as an attackers deployed contract. It was necessary because the AnySwap .underlying() method was called on it. Due to the lack of validation attacker could return any _underlying value. He returned WETH address, let’s see why.

The intended behaviour should be: user calls permit on WETH contract with v,r,s to approve the router the ability of withdrawing tokens. But, the WETH token has no permit method!

However, the WETH has the fallback function. And once the WETH can’t distinguish what function to call, since there is no permit method, the fallback would be called

solidity fallback function in the old versions
solidity fallback function in the old versions

The function TransferHelper.safeTransferFrom(_underlying, from, token, amount); was originally expected to operate under the assumption that the signature verification in the preceding step was successful. This would imply that the approval granted by the signature could be used to transfer the specified amount from the user's account to the router. However, as observed, the signature validation did not occur as intended.

In theory, this should not have been an issue because, although the attacker’s input should not have passed the signature check, the router would not have been approved to transfer the funds on behalf of the victim. However, the AnySwap had requested an almost infinite approval limit from all its users to save on gas fees.

Eventually, the problems were because of the following:

  1. Not useful function, which wasn’t used by the user at all (no code simplicity)

  2. Vulnerable function didn’t validate that the token inputed is indeed a valid AnySwap token.

  3. Vulnerable function didn’t validate that the permit call was actually called

Subscribe to Arsen
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.