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 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.
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, 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:
- Lead-In: Top Ten "Awesome" Security Incidents in 2023
- #1: Harvesting MEV Bots by Exploiting Vulnerabilities in Flashbots Relay
- #2: Euler Finance Incident: The Largest Hack of 2023
- #3: KyberSwap Incident: Masterful Exploitation of Rounding Errors with Exceedingly Subtle Calculations
- #4: Curve Incident: Compiler Error Produces Faulty Bytecode from Innocent Source Code
- #5: Platypus Finance: Surviving Three Attacks with a Stroke of Luck
- #6: Hundred Finance Incident: Catalyzing the Wave of Precision-Related Exploits in Vulnerable Forked Protocols
- #8: SushiSwap Incident: A Clumsy Rescue Attempt Leads to a Series of Copycat Attacks
- #9: MEV Bot 0xd61492: From Predator to Prey in an Ingenious Exploit
- #10: ThirdWeb Incident: Incompatibility Between Trusted Modules Exposes Vulnerability