Analysis of the Hedgey Finance Exploit

5 min read

Learn how Hedgey Finance was exploited, resulting in a loss of assets worth $44.7 million.

TL;DR#

On April 19, 2024, Hedgey Finance was exploited across a series of transactions, which resulted in a loss of $2.1 million on the Ethereum Mainnet and $42.6 million worth of assets on the Arbitrum network, totaling approximately $44.7 million.

Introduction to Hedgey#

Hedgey Finance helps teams create and manage on-chain token vesting, lockups, claim portals, and more.

Vulnerability Assessment#

The root cause of the exploit is the lack of input validation on users' parameters, which allowed the attacker to manipulate and gain unauthorized token approvals.

Steps#

Step 1:

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

Step 2:

The attacker took a flash loan of $1.3 million USDC from Balancer to abuse and manipulate the `claimLockup` parameter within the `createLockedCampaign` function of the exploited contract to trick this vulnerable contract into approving USDC token transfer to the attack contract.

/// @notice primary function for creating an locked or vesting claims campaign. This function will pull the amount of tokens in the campaign struct, and map the campaign and claimLockup to the id.
/// additionally it will check that the lockup details are valid, and perform an allowance increase to the contract for when tokens are claimed they can be pulled.
/// @dev the merkle tree needs to be pre-generated, so that you can upload the root and the uuid for the function
/// @param id is the uuid or CID of the file that stores the merkle tree
/// @param campaign is the struct of the campaign info, including the total amount tokens to be distributed via claims, and the root of the merkle tree, plus the lockup type of either 1 (lockup) or 2 (vesting)
/// @param claimLockup is the struct that defines the characteristics of the lockup for each token claimed.
/// @param donation is the doantion struct that can be 0 or any amount of tokens the team wishes to donate
function createLockedCampaign(bytes16 id, Campaign memory campaign, ClaimLockup memory claimLockup, Donation memory donation) external nonReentrant {
  require(!usedIds[id], "in use");
  usedIds[id] = true;
  require(campaign.token != address(0), "0_address");
  require(campaign.manager != address(0), "0_manager");
  require(campaign.amount > 0, "0_amount");
  require(campaign.end > block.timestamp, "end error");
  require(campaign.tokenLockup != TokenLockup.Unlocked, "!locked");
  require(claimLockup.tokenLocker != address(0), "invalide locker");
  TransferHelper.transferTokens(campaign.token, msg.sender, address(this), campaign.amount + donation.amount);
  if (donation.amount > 0) {
    if (donation.start > 0) {
      SafeERC20.safeIncreaseAllowance(IERC20(campaign.token), donation.tokenLocker, donation.amount);
      ILockupPlans(donation.tokenLocker).createPlan(donationCollector, campaign.token, donation.amount, donation.start, donation.cliff, donation.rate, donation.period);
    } else {
      TransferHelper.withdrawTokens(campaign.token, donationCollector, donation.amount);
    }
    emit TokensDonated(id, donationCollector, campaign.token, donation.amount, donation.tokenLocker);
  }
  claimLockups[id] = claimLockup;
  SafeERC20.safeIncreaseAllowance(IERC20(campaign.token), claimLockup.tokenLocker, campaign.amount);
  campaigns[id] = campaign;
  emit ClaimLockupCreated(id, claimLockup);
  emit CampaignStarted(id, campaign);
}

Step 3:

The exploiter then invoked a call to the cancelCampaign function to cancel the campaign, which would then allow them to retrieve these approved and unclaimed assets.

/// @notice this function allows the campaign manager to cancel an ongoing campaign at anytime. Cancelling a campaign will return any unclaimed tokens, and then prevent anyone from claiming additional tokens
/// @param campaignId is the id of the campaign to be cancelled
function cancelCampaign(bytes16 campaignId) external nonReentrant {
  Campaign memory campaign = campaigns[campaignId];
  require(campaign.manager == msg.sender, "!manager");
  delete campaigns[campaignId];
  delete claimLockups[campaignId];
  TransferHelper.withdrawTokens(campaign.token, msg.sender, campaign.amount);
  emit CampaignCancelled(campaignId);
}

Step 4:

The attacker executed yet another transaction, likely to avoid being front-run by bots, to leverage the USDC approvals to transfer the approved tokens from the victim contract to themselves.

Step 5:

The victim contract on the Ethereum Mainnet was drained of assets, including USDC, NOBL, and MASA tokens. All parts of the stolen funds were swapped to DAI and then transferred to this EOA, which at the time of this writing holds approximately $2,173,325 worth of assets.

Step 6:

On the Arbitrum chain, according to Cyvers, the attacker was able to steal over 77.74 million BONUS tokens. At the time of this writing, this address, likely controlled by the hacker, has a hold of approximately $42,624,729 worth of assets.

Aftermath#

The team acknowledged the occurrence of the exploit on the Hedgey Token Claim Contract and urged community users to cancel their active claims in order to further minimize the impact caused by the exploit.

Solution#

To mitigate the vulnerabilities exploited in the Hedgey Finance smart contract and enhance overall security, several comprehensive steps should be undertaken. Initially, improving input validation across all contract functions is crucial, particularly in functions that can alter contract states. This includes implementing strict boundary conditions for input parameters to prevent manipulations like unauthorized token allowances. Access control also needs strengthening by introducing role-based permissions to ensure that only authorized users can perform sensitive operations. Multi-factor authentication could be added to these operations to provide an additional security layer.

Secure patterns for handling token transfers and allowances must be implemented. This could involve modifying the logic that sets token allowances, perhaps by requiring manual confirmation for increases beyond certain thresholds, or by implementing caps on maximum allowable token transfers. Regular, independent security audits from reputable third-party firms are essential. These audits should include both automated scanning and manual code review. Additionally, setting up continuous monitoring tools to analyze and identify potential security vulnerabilities promptly is advisable.

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 Hedgey Finance 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 BlockSec

By

Tags