Analysis of the OSN Token Exploit

4 min read

Learn how OSN Token was exploited, resulting in a loss of assets worth $110,000.

TL;DR#

On May 6, 2024, the OSN token was exploited across a series of transactions on the BNB chain, which resulted in a loss of assets worth approximately $110,000. 

Introduction to OSN Token#

OSN is a token on the BNB chain.

Vulnerability Assessment#

The root cause of the exploit is due to flawed control mechanisms on transfer and reward distribution, allowing the attacker to manipulate the reward payout.

Steps#

Step 1:

We attempt to analyse one of the attack transactions executed by the exploiter.

Step 2:

It can be seen in the OSNLpDividendTracker contract that the contract sells its own tokens as a reward to users for adding liquidity.

Step 3:

In the transfer function of the OSN token contract, if the transfer to address is the address of a liquidity pool, it uses the swapAndSendDividends function to sell its own tokens and sends the BUSD assets to the OSNLpDividendTracker.

function _transfer(address from, address to, uint256 amount) internal override {
  require(from != address(0), "ERC20: transfer from the zero address");
  require(to != address(0), "ERC20: transfer to the zero address");

  require(!_blacklist[from], "blacklists");
  require(balanceOf(from) >= amount, "error");

  if (amount == 0) {
    super._transfer(from, to, 0);
    return;
  }

  UserInfo storage userInfo;

  uint256 addLPLiquidity;
  bool isAddLP;
  if (to == uniswapV2Pair && address(uniswapV2Router) == msg.sender) {
    addLPLiquidity = _isAddLiquidity(amount);
    if (addLPLiquidity > 0) {
      isAddLP = true;
      userInfo = _userInfo[from];
      userInfo.lpAmount += addLPLiquidity;
      try lpDividendTracker.setBalance(payable(from), userInfo.lpAmount) {} catch {}
    }
  }

  uint256 removeLPLiquidity;
  if (from == uniswapV2Pair) {
    removeLPLiquidity = _strictCheckBuy(amount);
    if (removeLPLiquidity > 0) {
      if (!_isExcludedFromFees[to]) {
        require(_userInfo[to].lpAmount >= removeLPLiquidity);
      }
      uint256 blp = IUniswapV2Pair(uniswapV2Pair).balanceOf(to);
      _userInfo[to].lpAmount = blp;
      try lpDividendTracker.setBalance(payable(to), blp) {} catch {}
    }
  }

  if (!_isExcludedFromFees[from] && !_isExcludedFromFees[to]) {
    if (!isAddLP && !swapping && automatedMarketMakerPairs[to] && from != owner() && to != owner()) {
      swapping = true;
      uint256 sellAmount = 0;
      if (block.timestamp <= startSwapTime + 30 days) {
        sellAmount = (amount * 12) / 100;
      } else if (block.timestamp <= startSwapTime + 60 days) {
        sellAmount = (amount * 4) / 100;
      } else if (block.timestamp <= startSwapTime + 90 days) {
        sellAmount = (amount * 6) / 100;
      } else if (block.timestamp <= startSwapTime + 120 days) {
        sellAmount = (amount * 8) / 100;
      }

      if (sellAmount > 0 && balanceOf(address(this)) >= sellAmount) {
        swapAndSendDividends(sellAmount);
        burnPoolToken(sellAmount);
      }
      uint256 contractTokenBalance = balanceOf(taxAddress);
      bool canSwap = contractTokenBalance >= swapTokensAtAmount;
      if (canSwap) {
        swapAndAddLiqidity(contractTokenBalance);
      }
      swapping = false;
    }
    require(block.timestamp >= startSwapTime, "not start");

    uint256 fees;
    if (automatedMarketMakerPairs[from]) {
      fees = amount.mul(buyFees).div(1000);
      if (block.timestamp < startSwapTime + 3 && !automatedMarketMakerPairs[to]) {
        _blacklist[to] = true;
      }
      if (block.timestamp < startSwapTime + 5 minutes) {
        require(amount <= txLimit, "txlimit");
      }
    } else if (automatedMarketMakerPairs[to]) {
      if (isAddLP) {
        fees = amount.mul(35).div(1000);
      } else {
        fees = amount.mul(sellFees).div(1000);
      }
    }
    super._transfer(from, taxAddress, fees);
    amount -= fees;
  }
  super._transfer(from, to, amount);

  if (!swapping) {
    uint256 gas = gasForProcessing;
    try lpDividendTracker.process(gas) returns (uint256 iterations, uint256 claims, uint256 lastProcessedIndex) {
      emit ProcessedDividendTracker(iterations, claims, lastProcessedIndex, true, gas, tx.origin);
    } catch {}
  }
}
function swapAndSendDividends(uint256 tokenAmount) private {
  _approve(address(this), address(uniswapV2Router), tokenAmount);
  address[] memory path = new address[](2);
  path[0] = address(this);
  path[1] = _baseToken;
  uniswapV2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(tokenAmount, 0, path, address(_tokenDistributor), block.timestamp);
  uint256 totalAmount = IERC20(_baseToken).balanceOf(address(_tokenDistributor));
  IERC20(_baseToken).transferFrom(address(_tokenDistributor), address(lpDividendTracker), totalAmount);
  try lpDividendTracker.distributeDividends(totalAmount) {} catch {}
}

Step 4:

The attacker repeatedly invoked these calls to purchase and sell OSN tokens, thereby causing the OSNLpDividendTracker contract to accumulate a large amount of rewards.

Step 5:

As the OSNLpDividendTracker contract primarily distributes rewards to addresses that provide the liquidity, the attacker used multiple attack contracts to add liquidity and gain the rewards from the OSNLpDividendTracker contract.

Solution#

Addressing the vulnerabilities exploited in the OSN token requires a comprehensive approach that starts with identifying and rectifying the specific security weaknesses in the token's contract logic. The primary focus should be on the vulnerable functions, which are integral to how rewards are distributed. This function must be redesigned to prevent misuse where tokens can be sold in large quantities in a way that disproportionately benefits any entity or group of entities, particularly those that can orchestrate a series of rapid transactions to manipulate reward outcomes.

To prevent similar exploits in the future, it is crucial to introduce additional checks and balances within the transfer and reward mechanisms. For instance, implementing a mechanism that limits the frequency and total volume of transactions that an individual address can initiate within a certain timeframe could mitigate rapid exploitation loops.

Even with stringent security measures in place, it is impossible to completely rule out the exploitation of vulnerabilities. In these situations, the involvement of Neptune Mutual is critical. By setting up a dedicated cover pool with Neptune Mutual, the adverse effects of incidents like the OSN Token exploit can be substantially mitigated. Neptune Mutual excels in providing coverage for losses due to smart contract vulnerabilities, employing parametric policies designed specifically for these distinct risks.

Collaborating with Neptune Mutual simplifies the recovery process for users by reducing the requirement for detailed proof of loss documentation. Once an incident is confirmed and resolved through our detailed incident resolution protocol, our focus quickly turns to promptly delivering compensation and financial support to those affected. This approach ensures rapid assistance for users impacted by such security breaches.

Our coverage extends across several major blockchain platforms, including EthereumArbitrum, and the BNB chain, providing widespread support to a diverse range of DeFi users. This extensive coverage strengthens our ability to safeguard against various vulnerabilities, thus improving the overall security of our wide client base.

Reference Source ChainAegis

By

Tags