I accidentally sent some WETH to a contract, can you help me?
Here, we have 3 files here MasterChefHelper.solSetup.solUniswapV2Like.sol
The vulnerable contract is MasterChefHelper.sol (UniswapV2Like.sol is useless here).
First, The Setup.sol contract is creating the CTF, the contract is creating a vulnerable fork of a MasterChef (from Sushi).
Then we can see the whoops comment in the code below, because the admin sent 10 weth at the wrong address… So we have to steal the 10 weth from the MasterChefHelper.
// SPDX-License-Identifier: UNLICENSED
pragma solidity0.8.16;import"./UniswapV2Like.sol";interfaceERC20Like{functiontransferFrom(address,address,uint)external;functiontransfer(address,uint)external;functionapprove(address,uint)external;functionbalanceOf(address)externalviewreturns(uint);}interfaceMasterChefLike{functionpoolInfo(uint256id)externalreturns(addresslpToken,uint256allocPoint,uint256lastRewardBlock,uint256accSushiPerShare);}contractMasterChefHelper{MasterChefLikepublicconstantmasterchef=MasterChefLike(0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd);UniswapV2RouterLikepublicconstantrouter=UniswapV2RouterLike(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F);functionswapTokenForPoolToken(uint256poolId,addresstokenIn,uint256amountIn,uint256minAmountOut)external{(addresslpToken,,,)=masterchef.poolInfo(poolId);addresstokenOut0=UniswapV2PairLike(lpToken).token0();addresstokenOut1=UniswapV2PairLike(lpToken).token1();ERC20Like(tokenIn).approve(address(router),type(uint256).max);ERC20Like(tokenOut0).approve(address(router),type(uint256).max);ERC20Like(tokenOut1).approve(address(router),type(uint256).max);ERC20Like(tokenIn).transferFrom(msg.sender,address(this),amountIn);// swap for both tokens of the lp pool
_swap(tokenIn,tokenOut0,amountIn/2);_swap(tokenIn,tokenOut1,amountIn/2);// add liquidity and give lp tokens to msg.sender
_addLiquidity(tokenOut0,tokenOut1,minAmountOut);}function_addLiquidity(addresstoken0,addresstoken1,uint256minAmountOut)internal{(,,uint256amountOut)=router.addLiquidity(token0,token1,ERC20Like(token0).balanceOf(address(this)),ERC20Like(token1).balanceOf(address(this)),0,0,msg.sender,block.timestamp);require(amountOut>=minAmountOut);}function_swap(addresstokenIn,addresstokenOut,uint256amountIn)internal{address[]memorypath=newaddress[](2);path[0]=tokenIn;path[1]=tokenOut;router.swapExactTokensForTokens(amountIn,0,path,address(this),block.timestamp);}}
1. What the contract does?
The function swapTokenForPoolToken
This is the function, we will use to the steal the 10 weth from the MasterChefHelper.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
functionswapTokenForPoolToken(uint256poolId,addresstokenIn,uint256amountIn,uint256minAmountOut)external{(addresslpToken,,,)=masterchef.poolInfo(poolId);// get the LPaddress from the poolInfo
addresstokenOut0=UniswapV2PairLike(lpToken).token0();//get the token0 of the pool
addresstokenOut1=UniswapV2PairLike(lpToken).token1();//get the token0 of the pool
ERC20Like(tokenIn).approve(address(router),type(uint256).max);ERC20Like(tokenOut0).approve(address(router),type(uint256).max);ERC20Like(tokenOut1).approve(address(router),type(uint256).max);ERC20Like(tokenIn).transferFrom(msg.sender,address(this),amountIn);//Tranfer the tokenIn to the masterchef
// swap for both tokens of the lp pool
_swap(tokenIn,tokenOut0,amountIn/2);//swap half of the amount
_swap(tokenIn,tokenOut1,amountIn/2);//swap half of the amount
// add liquidity and give lp tokens to msg.sender
_addLiquidity(tokenOut0,tokenOut1,minAmountOut);}
So this function swap our tokenIn (for example USDCtotoken0 & token1 of the Liquidity Pool given into poolId).
Then will make a Liquidity Pool Token (LP) from token1 and token0 and sending back to msg.sender.
This is really similar to the Zap method from StellaSwap on Moonbeam 😎
But in our CTF, we cannot swap identical tokens (for example, if tokenIn is USDC the pool has to be something that doesn’t contain any USDC inside otherwise the _swap(tokenIn, tokenOut1, amountIn / 2); will revert with the message IDENTICAL SWAP TOKEN.
So this pool will work DAI/WETH but USDC/WETH will revert.
So now we understood that, our attack goal will be to trigger _addLiquidity(tokenOut0, tokenOut1, minAmountOut); with token0DAI and token1WETH to get the 10 WETH inside the contract.
1
2
3
4
5
6
7
8
function_addLiquidity(addresstoken0,addresstoken1,uint256minAmountOut)internal{(,,uint256amountOut)=router.addLiquidity(token0,token1,ERC20Like(token0).balanceOf(address(this)),ERC20Like(token1).balanceOf(address(this)),0,0,msg.sender,block.timestamp);require(amountOut>=minAmountOut,'minAmoun to slow');}
The vulnerability is here because the MasterChefHelper is using ERC20Like(token1).balanceOf(address(this)) & ERC20Like(token0).balanceOf(address(this)).
A check on the swap return value should be implemented here!
1
2
3
4
5
6
7
8
9
10
11
12
13
//Eexplanation code
uint256realvalue0=_swap(tokenIn,tokenOut0,amountIn/2);uint256realvalue1=_swap(tokenIn,tokenOut1,amountIn/2);// DO NOT USE IN PROD
(,,uint256amountOut)=router.addLiquidity(token0,token1,ERC20Like(token0).balanceOf(address(this)),ERC20Like(token1).balanceOf(address(this)),0,0,msg.sender,block.timestamp);// Patch version
(,,uint256amountOut)=router.addLiquidity(token0,token1,realvalue0,realvalue1,0,0,msg.sender,block.timestamp);
Using BalanceOf(address(this))is dangerous here because if you have enough token0 or token1 then the pool will be created and sent back the msg.sender.
Now, we know that we have to swap some WETH to USDC and find a pool that doesn’t contain USDC inside but WETH is mandatory (as we said before we will choose DAI/WETH).
Perfect! the pool number 2 match exactly what we need :
If we just call the swapTokenForPoolToken() with some USDC it will just create a “normal” LP with USDC/2 ⇒ token0 & USDC/2 ⇒ token1. And you cannot withdraw the 10 WETH.
But the trick here is to send more DAI to MasterChefHelper directly.
So as we said last time MasterChefHelper will use ERC20Like(token0).balanceOf(address(this)) and create a pool with the total value and sending back to you 😉
Then the pool will be empty! We then just has to flag the challenge using netcat!