Home - Coinspect Security

Seaman

Total Losses

$7.0K+

Date

Network

binance smart chain logo binance smart chain

Categories

business logic price manipulation

Step-by-step

  1. Flashloan some USDC
  2. Use the flashloan to buy all GVC in a pool
  3. Call transfer() so contract buys GVC at current high price
  4. Sell your GVC
  5. Return flashloan

Detailed Description

This is very similar to the attack on MBC. It actually involves the same method, swapAndLiquifyV1.

Every time someone called _transfer on Seaman, the swapAndLiquify calls where made. These methods would exchange the accumulated fee on the contract for GVC, passing through the BUSD pool.

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
         // ...
         if( uniswapV2Pair.totalSupply() > 0 && balanceOf(address(this)) > balanceOf(address(uniswapV2Pair)).div(10000) && to == address(uniswapV2Pair)){
            if (
                !swapping &&
                _tokenOwner != from &&
                _tokenOwner != to &&
               !ammPairs[from] &&
                !(from == address(uniswapV2Router) && !ammPairs[to])&&
                swapAndLiquifyEnabled
            ) {
                swapping = true;
                swapAndLiquifyV3();
                swapAndLiquifyV1();
                swapping = false;
            }
        }
       // ...
    }

    function swapAndLiquifyV1() public {
        uint256 canlpAmount = lpAmount.sub(lpTokenAmount);
        uint256 amountT = balanceOf(address(uniswapV2Pair)).div(10000);
        if(balanceOf(address(this)) >= canlpAmount && canlpAmount >= amountT){
            if(canlpAmount >= amountT.mul(5))
                canlpAmount = amountT.mul(5);
            lpTokenAmount = lpTokenAmount.add(canlpAmount);
            uint256 beflpBal = lpToken.balanceOf(address(this));
            swapTokensFor(canlpAmount,address(lpToken),address(this));
            uint256 newlpBal = lpToken.balanceOf(address(this)).sub(beflpBal);
            lpDivTokenAmount = lpDivTokenAmount.add(newlpBal);
            isLpProc = true;
        }
    }

    function swapAndLiquifyV3() public {
        uint256 canhAmount = hAmount.sub(hTokenAmount);
        uint256 amountT = balanceOf(address(uniswapV2Pair)).div(10000);
        if(balanceOf(address(this)) >= canhAmount && canhAmount >= amountT){
            if(canhAmount >= amountT.mul(5))
                canhAmount = amountT.mul(5);
            hTokenAmount = hTokenAmount.add(canhAmount);
            uint256 befhBal = hToken.balanceOf(address(this));
            swapTokensFor(canhAmount,address(hToken),address(this));
            uint256 newhBal = hToken.balanceOf(address(this)).sub(befhBal);
            hDivTokenAmount = hDivTokenAmount.add(newhBal);
            isHProc = true;
        }
    }

This makes it possible for an attacker to manipulate the price and force the contract to buy tokens at a high price. In this case, the attacker influenced the price of GVC by requesting a flashloan of BUSD and buying up all the available liquidity of GVC in the pool and then calling transfer() on Seaman.

Possible mitigations

  1. Prevent users to manipulate contract balances via low liquidity pair interactions.
  2. Do not automatically perform trades without a sanity check on the prices