How Was Exactly Protocol Exploited?

5 min read

Learn how the Exactly Protocol was exploited, resulting in a loss of funds worth 4324 ETH.

TL;DR#

On August 18, 2023, the Exactly Protocol was exploited across multiple transactions on the Optimism chain, which resulted in a loss of over 4324 ETH, totaling approximately $7.3 million.

Introduction to Exactly Protocol#

Exactly is a decentralized, non-custodial, open-source protocol providing an autonomous fixed and variable interest rate market.

Vulnerability Assessment#

The vulnerable contract lacked input validation, which allowed the attacker to create a malicious contract that stole funds from the users and gained incentives by liquidating their bad debt position.

Steps#

Step 1:

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

Step 2:

The `leverage` function in the DebtManager contract doesn't verify the input address, so anyone could create a malicious contract to force the DebtManager to create a position in the UniswapV3 contract with the WETH and a fake token pair.

function leverage(Market market, uint256 deposit, uint256 ratio, uint256 borrowAssets, Permit calldata marketPermit)
  external
  permit(market, borrowAssets, marketPermit)
  msgSender
{
  market.asset().safeTransferFrom(msg.sender, address(this), deposit);
  noTransferLeverage(market, deposit, ratio);
}
modifier permit(ERC20 token, uint256 assets, Permit calldata p) {
  IERC20PermitUpgradeable(address(token)).safePermit(p.account, address(this), assets, p.deadline, p.v, p.r, p.s);
  {
    address sender = _msgSender;
    if (sender == address(0)) _msgSender = p.account;
    else assert(p.account == sender);
  }
  _;
  assert(_msgSender == address(0));
}
function noTransferCrossLeverage(
  Market marketIn,
  Market marketOut,
  uint24 fee,
  uint256 deposit,
  uint256 ratio,
  uint160 sqrtPriceLimitX96
) internal {
  LeverageVars memory v;
  v.assetIn = address(marketIn.asset());
  v.assetOut = address(marketOut.asset());
  v.sender = _msgSender;

  v.amount = crossPrincipal(marketIn, marketOut, deposit, v.sender).mulWadDown(ratio) - marketIn.maxWithdraw(v.sender)
    - deposit;
  if (v.amount > 0) {
    PoolKey memory poolKey = PoolAddress.getPoolKey(v.assetIn, v.assetOut, fee);
    IUniswapV3Pool(PoolAddress.computeAddress(uniswapV3Factory, poolKey)).swap(
      address(this),
      v.assetOut == poolKey.token0,
      -int256(v.amount),
      sqrtPriceLimitX96,
      abi.encode(
        SwapCallbackData({
          marketIn: marketIn,
          marketOut: marketOut,
          assetIn: v.assetIn,
          assetOut: v.assetOut,
          principal: deposit,
          account: v.sender,
          fee: fee,
          leverage: true
        })
      )
    );
  } else {
    marketIn.deposit(deposit, v.sender);
  }
}

Step 3:

The attacker was able to bypass the permit check in the leverage function by directly passing a fake market address without validation and changing the `_msgSender` to the victim address.

Step 4:

The attacker then invoked a call to the crossDeleverage function, thereby stealing the WETH token by burning the exaWETH token approved by the victims and then transferring the WETH to the UniswapV3 pool.

function crossDeleverage(
  Market marketIn,
  Market marketOut,
  uint24 fee,
  uint256 withdraw,
  uint256 ratio,
  uint160 sqrtPriceLimitX96
) public msgSender {
  LeverageVars memory v;
  v.assetIn = address(marketIn.asset());
  v.assetOut = address(marketOut.asset());
  v.sender = _msgSender;

  v.amount = floatingBorrowAssets(marketOut)
    - (
      ratio > 1e18
        ? previewAssetsOut(
          marketIn, marketOut, (crossPrincipal(marketIn, marketOut, 0, v.sender) - withdraw).mulWadDown(ratio - 1e18)
        )
        : 0
    );

  PoolKey memory poolKey = PoolAddress.getPoolKey(v.assetIn, v.assetOut, fee);
  IUniswapV3Pool(PoolAddress.computeAddress(uniswapV3Factory, poolKey)).swap(
    address(this),
    v.assetIn == poolKey.token0,
    -int256(v.amount),
    sqrtPriceLimitX96,
    abi.encode(
      SwapCallbackData({
        marketIn: marketIn,
        marketOut: marketOut,
        assetIn: v.assetIn,
        assetOut: v.assetOut,
        principal: withdraw,
        account: v.sender,
        fee: fee,
        leverage: false
      })
    )
  );
}

Step 5:

The exploiter transferred any WETH token in UniswapV3pool to him by closing the position and collecting WETH and fake token amounts.

Step 6:

After withdrawing the collateral asset of the victim's address, the collateral of the victim is reduced, and then the attacker executes the liquidate function to earn the liquidator incentive.

Step 7:

The other attack transactions in reference are this and this, and the total loss for the protocol was roughly 4324 ETH, or around $7.3 million. Approximately 1500 ETH have been bridged back to Ethereum through the Across Bridge, while 2833 ETH are still in the process of being bridged back to Ethereum via the Optimism Bridge.

Image: Funds flow for Exactly Protocol exploit. Courtesy of MetaSleuth by BlockSec

Aftermath#

The team acknowledged the occurrence of the exploit and stated that they paused the protocol while investigating the issue. They also sent an on-chain message to the hacker to speak about the next set of actions.

They stated that they were planning an upgrade to fix the affected periphery contract and that the protocol will be unpaused on August 19, following a 24-hour time lock period.

Solution#

The exploit of the Exactly Protocol highlights fundamental flaws in input validation, authentication mechanisms, and internal logic interactions. The heart of the issue was the ability to provide a spurious market address for the leverage function. This can be mitigated by implementing a whitelisting mechanism. It should only be possible to use market addresses that the protocol recognizes. By maintaining an on-chain list of authentic market addresses, the protocol can cross-reference any input against this list, rejecting those that aren't recognized.

Not all audits are created equal. Multiple audits don't necessarily cover all aspects of a protocol. It's possible that all these audits focused on similar areas or utilized similar methodologies, leaving blind spots in the assessment. While private audits are essential and offer a controlled evaluation environment, public audits and open-source scrutiny can complement them by providing diversity in assessment and ongoing vigilance.

The team should also integrate mechanisms to pause specific functionalities during anomalies. This rapid response tool can serve as a first line of defense against unforeseen vulnerabilities, stalling malicious activities while a more permanent solution is devised.

Although rigorous security measures are essential, vulnerabilities can sometimes emerge. This is where Neptune Mutual becomes invaluable, offering protection for users. Had Exactly Protocol partnered with Neptune Mutual to set up a specific coverage pool before the incident, the fallout might have been significantly mitigated. At Neptune Mutual, we understand the intricacies of the DeFi landscape, which is why we offer coverage to users who might face losses from vulnerabilities in smart contracts.

Our avant-garde parametric policies ensure that users are not mired in bureaucratic processes when proving their losses. Instead, they can readily claim their payouts, post an incident's confirmation and resolution through our rigorous incident resolution framework.

Our marketplace is available on several renowned blockchain networks, including EthereumArbitrum, and the BNB chain. With this expansive network, we cater to a vast range of DeFi enthusiasts, shielding them from potential risks and bolstering their trust in the ecosystem.

Reference Source BlockSec

By

Tags