Understanding the Astrid Finance Exploit

5 min read

Learn how Astrid Finance was exploited, resulting in a loss of assets worth $228,000.

TL;DR#

On October 28, 2023, Astrid Finance was exploited on the Ethereum Mainnet due to a smart contract vulnerability, which resulted in a loss of funds worth approximately $228,000.

Introduction to Astrid Finance#

Astrid Finance is the liquid restaking pool on Ethereum, powered by the Eigen Layer.

Vulnerability Assessment#

The root cause of the exploit is a lack of input validation.

Steps#

Step 1:

We attempt to analyze the attack transaction executed by the exploiter.

Step 2:

It can be seen on the vulnerable contract that the withdraw function lacked input validation, thus making its parameters `_restakedTokenAddress` and `amount` manipulable.

function withdraw(address _restakedTokenAddress, uint256 amount) public nonReentrant whenNotPaused {
  require(Utils.contractExists(_restakedTokenAddress), "AstridProtocol: Contract does not exist");
  require(amount > 0, "AstridProtocol: Amount must be greater than 0");
  require(IERC20(_restakedTokenAddress).balanceOf(msg.sender) >= amount, "AstridProtocol: Insufficient balance of restaked token");
  require(IERC20(_restakedTokenAddress).allowance(msg.sender, address(this)) >= amount, "AstridProtocol: Insufficient allowance of restaked token");

  uint256 sharesBefore = IRestakedETH(_restakedTokenAddress).scaledBalanceOf(address(this));

  // receive restaked token from user to "lock" it
  bool amountSent = Utils.payMe(msg.sender, amount, _restakedTokenAddress);
  require(amountSent, "AstridProtocol: Failed to send restaked token");

  uint256 sharesAfter = IRestakedETH(_restakedTokenAddress).scaledBalanceOf(address(this));
  uint256 shares = sharesAfter.sub(sharesBefore); // we store shares of restakedETH to ensure that it is still subject to rebase when locked

  WithdrawalRequest memory request = WithdrawalRequest({
    withdrawer: msg.sender,
    restakedTokenAddress: _restakedTokenAddress,
    amount: amount,
    requestedRestakedTokenShares: shares,
    claimableStakedTokenAmount: 0, // placeholder
    status: WithdrawalStatus.REQUESTED,
    withdrawalStartBlock: uint32(block.number),
    withdrawRequestedAt: block.timestamp,
    withdrawProcessedAt: 0,
    withdrawClaimedAt: 0,
    withdrawalRequestsIndex: withdrawalRequests.length,
    withdrawalRequestsByUserIndex: withdrawalRequestsByUser[msg.sender].length
  });

  totalWithdrawalRequests[_restakedTokenAddress] += shares;
  withdrawalRequests.push(request);
  withdrawalRequestsByUser[msg.sender].push(request);

  emit WithdrawalRequested(msg.sender, _restakedTokenAddress, amount, shares);

  if (processWithdrawalsOnWithdraw) {
    _processWithdrawals();
  }
}

Step 3:

Essentially, this function failed to validate the staked token address, which allowed the attacker to deploy fake tokens, mint them, and claim the allowance to drain funds from the pool.

library Utils {
  using SafeMath for uint256;
  using SafeERC20 for IERC20;

  function payMe(address payer, uint256 amount, address token) internal returns (bool) {
    return payTo(payer, address(this), amount, token);
  }

  function payTo(address allower, address receiver, uint256 amount, address token) internal returns (bool) {
    // Request to transfer amount from the contract to receiver.
    // Contract does not own the funds, so the allower must have added allowance to the contract
    // Allower is the original owner.
    return IERC20(token).transferFrom(allower, receiver, amount);
  }

  function payDirect(address to, uint256 amount, address token) internal returns (bool) {
    IERC20(token).safeTransfer(to, amount);
    return true;
  }

  function contractExists(address contractAddress) internal view returns (bool) {
    uint32 size;
    assembly {
      size := extcodesize(contractAddress)
    }
    return size > 0;
  }
}

Step 4:

The attacker created three fake tokens and used them in succession to withdraw and claim stETH, rETH, and cbETH.

undefined

Step 5:

The assets in possession, 64.17 stETH worth approximately $114,757, 39.16 rETH worth approximately $76,328.5, and 20.0004 cbETH worth approximately $37,637.2, were then converted to 127.797 ETH, worth approximately $228,591.

undefined

Aftermath#

The team acknowledged the occurrence of the incident and stated that the exploit in reference was missed by their team. They have paused the vulnerable contracts, taken a snapshot of all of their token holders, and will be working on procedures to offer a full refund to all of the affected individuals.

They also sent an on-chain message to the hacker offering a bounty of 20% of the stolen funds, with hopes of retrieving the remaining 80% of it. They shared a full list of refunds from all depositors that would likely be processed in the days to come and later acknowledged that all refunds had been processed. Apparently, the hacker has already returned 80% of the stolen assets.

Solution#

Addressing the Astrid Finance Exploit demands a multi-faceted approach that takes into consideration not only the coding shortcomings of the smart contract but also the permissions and privileges given to it. The vulnerability was largely due to the contract's failure to validate the functions properly. This oversight stresses the significance of thorough validation and parameter checks in contract functions.

The primary cause of the exploit was the lack of a rigorous validation mechanism for input parameters, especially those tied to monetary transfers. Addressing this could be achieved by incorporating lists of whitelisted token addresses that the contract interacts with, preventing unknown or malicious addresses from interacting with critical functions.

It's also imperative for DeFi protocols like Astrid Finance to invest in comprehensive audits by reputable blockchain security firms. These external evaluations serve as an additional layer of validation, ensuring that the smart contract's intricacies are thoroughly examined for potential vulnerabilities. Furthermore, reaching out to independent security researchers for a full-scale audit can bring diverse perspectives and expertise, illuminating any overlooked attack vectors. Such proactive steps not only identify and mitigate possible risks but also amplify the trust and confidence of the user base.

However, in the ever-evolving realm of DeFi and smart contracts, vulnerabilities can sometimes be overlooked despite the best precautions. This unpredictability accentuates the need for robust safeguarding measures, akin to what Neptune Mutual provides. Had Astrid Finance proactively collaborated with Neptune Mutual to establish a dedicated cover pool, the financial consequences of this exploit might have been significantly curtailed. Such covers act as a financial cushion, granting users a channel to recuperate possible financial losses due to smart contract glitches.

In partnering with Neptune Mutual, users are relieved of the typically intricate procedure of showcasing detailed proofs of their losses. After an incident is verified and resolved using our incident management structure, our primary focus is the swift allocation of compensations, ensuring expedited financial reprieve for the affected parties.

Functioning across multiple blockchain networks such as EthereumArbitrum, and the BNB chain, Neptune Mutual is devoted to offering its protective umbrella to an expansive spectrum of participants in the DeFi ecosystem. Our relentless commitment to user safety fosters enhanced trust within the DeFi sector, fortifying assurance, especially following significant security lapses like the Astrid Finance exploit.

Reference Source BlockSec

By

Tags