- Type: Exploit
- Network: Ethereum
- Total lost: 308 WETH (~ U$960K)
- Category: Data validation
- Exploited contracts:
- Attack transactions:
- Attacker Addresses:
- Attack Block:: 14037237
- Date: Jan 19, 2022
- Reproduce:
forge test --match-contract Exploit_Multichain -vvv
- Craft and deploy a contract so that it passes the requirements.
- Find a victim that had
permit
the contract to useWETH
. - Call
anySwapOutUnderlyingWithPermit
with your malicious contract and the victim's address.
Another attack that relies on an arbitry token
parameter. Here, Multichain intended the token to be an Anytoken
(Multichain was previously called AnySwap), which they use to track account balances when doing cross-chain transaction.
The anySwapOutUnderlyingWithPermit()
method takes a token
and will call permit
on its underlying and then transfer from the underlying
to the token
.
The contract fails to take into account that WETH
is special: WETH
's fallback function triggers its deposit()
method and returns true
and does not implement permit
, so calls to permit
on WETH
simply return true
.
To make matters worst, most of the users of Multichain had given an unlimited token allowance
to the Protocol, so when the contract uses transferFrom
it can use an arbitrary amount.
function anySwapOutUnderlyingWithPermit(
address from,
address token,
address to,
uint amount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s,
uint toChainID
) external {
address _underlying = AnyswapV1ERC20(token).underlying();
IERC20(_underlying).permit(from, address(this), amount, deadline, v, r, s);
TransferHelper.safeTransferFrom(_underlying, from, token, amount);
AnyswapV1ERC20(token).depositVault(amount, from);
_anySwapOut(from, token, to, amount, toChainID);
}
function _anySwapOut(address from, address token, address to, uint amount, uint toChainID) internal {
AnyswapV1ERC20(token).burn(from, amount);
emit LogAnySwapOut(token, from, to, amount, cID(), toChainID);
}
Here, the attacker deployed a contract that returned WETH
as the underlying.
permit
will pass due to the reason outlined above, even with no signature.transferFrom
will pass if thevictim
allowed Multichain withpermit
.
Then it is just a matter of findings victims.
- Implement a whitelist of allowed tokens.
- Avoid asking users to sign unlimited
allowances
.