#7: ParaSpace Incident: A Race Against Time to Thwart the Industry's Most Critical Attack Yet

ParaSpace Incident: A Race Against Time to Thwart the Industry's Most Critical Attack Yet

#7: ParaSpace Incident: A Race Against Time to Thwart the Industry's Most Critical Attack Yet

On March 17, 2023, due to a vulnerability in the price oracle, Paraspace became the target of a hacker attack. After the hacker's three failed attempts, the BlockSec Phalcon Block system intervened timely, saving over 5 million dollars worth of ETH through a rescue operation. Details and anecdotes about the rescue can be found at this link.

Click here to read the tweet

Background

ParaSpace (now known as Parallel Finance) is an NFT lending platform that allows you to utilize NFTs and ERC-20 tokens as collateral. By depositing these assets into ParaSpace, users can borrow ERC-20 tokens, offering the opportunity to generate returns on NFTs without the need to sell them.

One of the interesting features of ParaSpace is ApeStaking, which enables the auto-compounding of APE rewards. Users who engage in ApeStaking on the ParaSpace platform receive cAPE—a cToken representation of APE—which they can then contribute to the lending pool. Specifically, ParaSpace allows cAPE to be used as collateral for borrowing assets like USDC and WETH. Users can deposit cAPE tokens to obtain pcAPE shares. The collateral value is calculated by multiplying the amount of pcAPE with the rebasingIndex of the pcAPE token, as folllows:

  function _scaledBalanceOf(address user, uint256 rebasingIndex)
      internal
      view
      returns (uint256)
  {
      return super.scaledBalanceOf(user).rayMul(rebasingIndex);
  }

The pcAPE token's rebasingIndex is determined by the lastRebasingIndex function, which, in turn, invokes the getPooledApeByShares() function. The latter calculates the index using the formula sharesAmount.mul(_getTotalPooledApeBalance()).div(totalShares). The function _getTotalPooledApeBalance() represents the total value of APE tokens in the pools, while totalShares refers to the total supply of cAPE tokens.

  function lastRebasingIndex() internal view override returns (uint256) {
    return ICApe(_underlyingAsset).getPooledApeByShares(WadRayMath.RAY);
  }

  function getPooledApeByShares(uint256 sharesAmount)
      public
      view
      returns (uint256)
  {
      uint256 totalShares = _getTotalShares();
      if (totalShares == 0) {
          return 0;
      } else {
          return
              sharesAmount.mul(_getTotalPooledApeBalance()).div(totalShares);
      }
  }

The _getTotalPooledApeBalance() function is designed to provide the total sum of stakeAmount, rewardAmount, and bufferBalance as sourced from the ApeCoinStaking contract. More precisely, stakeAmount refers to the number of APE tokens that have been staked in the cAPE position within the ApeCoinStaking contract.

  function _getTotalPooledApeBalance()
      internal
      view
      override
      returns (uint256)
  {
      (uint256 stakedAmount, ) = apeStaking.addressPosition(address(this));
      uint256 rewardAmount = apeStaking.pendingRewards(
          APE_COIN_POOL_ID,
          address(this),
          0
      );
      return stakedAmount + rewardAmount + bufferBalance;

Vulnerability Analysis

Unfortunately, the earlier mentioned rebasingIndex is vulnerable to manipulation, which can artificially inflate the collateral value of the cAPE token.

Specifically, within the ApeCoinStaking contract, the depositApeCoin() function is designed to increase the stakedAmount for a given position. An exploit can occur if APE tokens are deposited into the cAPE position using the depositApeCoin() function, as this action can lead to an inflated output from the _getTotalPooledApeBalance() function, resulting in a distorted rebasingIndex.

  function depositApeCoin(uint256 _amount, address _recipient) public {
      if (_amount < MIN_DEPOSIT) revert DepositMoreThanOneAPE();
      updatePool(APECOIN_POOL_ID);

      Position storage position = addressPosition[_recipient];
      _deposit(APECOIN_POOL_ID, position, _amount);

      apeCoin.transferFrom(msg.sender, address(this), _amount);

      emit Deposit(msg.sender, _amount, _recipient);
  }

  function _deposit(uint256 _poolId, Position storage _position, uint256 _amount) private {
    Pool storage pool = pools[_poolId];
    _position.stakedAmount += _amount;
    pool.stakedAmount += _amount.toUint96();
    _position.rewardsDebt += (_amount * pool.accumulatedRewardsPerShare).toInt256();
  }

Attack Analysis

With the vulnerability identified, the mechanics of the attack become relatively straightforward. The attacker inflated the collateral value by depositing APE tokens through the depositApeCoin function, setting cAPE as the recipient, and then proceeded to borrow more tokens.

Take BlockSec's blocking transaction as an example, there are five key steps:

  • Obtaining a flash loan of approximately 47,352 wstETH. Supplying about 46,018 wstETH to borrow cAPE through the creation of multiple contracts.
  • Depositing approximately 12,880,000 cAPE to the protocol as collateral.
  • Trading about 1,205 wstETH for roughly 492,124 APE and withdraw 1,839,999 cAPE to APE tokens.
  • Depositing 2,332,214 APE tokens into the cAPE position by invoking ApeCoinStaking.depositApeCoin(), which changes the protocol's APE stakedAmount from 851,662 to 3,183,876, an increase of approximately 373%.
  • Leveraging the inflated collateral to borrow substantial amounts of various assets (i,e USDC, WETH) for profit.

Summary

In the ParaSpace incident, the price of pcAPE was manipulated by the attacker using a flashloan, exploiting the use of spot prices for calculation. This incident highlights the critical need for vigilance when incorporating price oracles into DeFi protocols, underscoring the necessity of ensuring that a protocol's price oracle is resistant to manipulation.

Moreover, given the escalating prevalence of security breaches in Web3, it is crucial for protocols to establish threat monitoring and prevention systems that remain active throughout their operational phases, not merely rely on pre-launch code audits. As demonstrated by the ParaSpace incident, BlockSec's Phalcon Block, which is capable of automatically blocking crypto hacks, managed to intervene ahead of the attacker, successfully safeguarding user assets.

Read other articles in this series:

Sign up for the latest updates