Back to all stories
Blogs
Incident Analysis
Prisma Finance Incident Analysis
3/29/2024
Prisma Finance Incident Analysis

Introduction

On 28 March, Prisma Finance was exploited by multiple addresses leading to a loss of approximately $12.3 million. Three attackers took advantage of a vulnerability in the Prisma Finance MigrateTroveZap contract which allowed them to manipulate a migration process. Since the exploit occurred, a wallet that received funds from the main exploiter has reached out to the Prisma Finance deployer declaring that their actions were a white hat rescue.

Vulnerability

The vulnerability that was exploited lies within the MigrateTroveZap contract which failed to validate data from onFlashloan() operations. This allowed the attacker to manipulate the Prisma Finance migration, which was advertized over the past few days, process to steal assets.

pf1

Specifically, the attacker was able to spoof migration data, which enabled unathorized transfers of collateral that led to direct losses for legitimate Prisma Finance users.

The MigrateTroveZap contract is used to migrate a user’s open position from one Trove Manager to another. This is done via the migrateTrove() function. In a normal migration operation, there are three primary steps:

Step 1 - The user calls migrateTrove() which will calculate the amount of collateral and debt that should be migrated to the new trove manager.

pf2

Step 2 - The above step will trigger the debtToken.flashloan() function with the given collateral and debt amounts of the debt token.

pf3

Step 3 - The debtToken.flashloan() function will then call back to the MigrateTroveZap.onFlashLoan() function to complete the migration.

pf4

The problem lies in step 3, as the data passed from onFlashLoan() does not conduct validation, meaning it trusts the data passed from the flashloan() call in the debt token contract. Normally this wouldn’t matter since the flashloan() function is called following the MigrateTroveZap.migrate() call. However, flashloan() can be called by any user as long as a fee is paid, therefore the data passed from the debt token contract to perform the migration is not trustable.

The attacker called the flashloan() function and set the receiver address to the MigrateTroveZap address and spoofed the data to perform a fake migration and steal user's collateral in later operations.

Attack Flow

This attack flow is based on the following transaction which has three primary steps:

  1. The attacker invoked mkUSD.flashLoan() with a value of 1442100643475620087665721 (1,442,100 mkUSD) and set the receiver to MigrateTroveZap contract.
  • 1442100643475620087665721(mkUSD) is the debt on the borrower address 0x56a201b872b50bbdee0021ed4d1bb36359d291ed

  • At the same time, the collateral for this address is 1745081655656230243345(1,745 wstETH)

  1. The MigrateTroveZap.onFlashLoan() function was triggered with attacker-crafted data due to the flashloan in step 1:
  • closeTrove
    • troveManager: 0x1cc79f3f47bfc060b6f761fcd1afc6d399a968b6

    • Account: 0x56a201b872b50bbdee0021ed4d1bb36359d291ed

    • data:(crafted by the attacker, as follows)

      borrower: 00000000000000000000000056a201b872b50bbdee0021ed4d1bb36359d291ed troveManagerFrom: 0000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b6 troveManagerTo: 0000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b6 maxFeePercentage: 0000000000000000000000000000000000000000000000000011c3794b4c52ff Coll: 463184447350099685758 0000000000000000000000000000000000000000000000191bf9b8cefc50317e upperHint: 000000000000000000000000e87c6f39881d5bf51cf46d3dc7e1c1731c2f790a lowerHint: 00000000000000000000000089ee26fcdff6b109f81abc6876600ec427f7907f

The ‘Coll’ field refers to the collateral for the new(migrated) trove, we can see it is much smaller than the previous collateral amount:

  • New collateral: 463184447350099685758 (463 wstETH)
  • Previous collateral: 1745081655656230243345(1,745 wstETH)
  • openTrove() was called and a new trove was opened based on the following calculation: 1745081655656230243345 - 463184447350099685758 = 1281897208306130557587 (1,281 wstETH) left in the MigrateTroveZap contract.
  1. The attacker created a position and withdrew the wstETH left in the MigrateTroveZapwith the following steps: a. Flashloan 1 wstETH and call openTrove().

b. Invoke MigrateTroveZap.onFlashLoan() again to migrate the new trove with crafted data. This time the ‘Coll' is 1282797208306130557587 which included the remaining wstETH amount in MigrateTroveZap.

c. The attacker closed the trove and received 1282797208306130557587 (1,282 wstETH).

d. Repaid the 1 wstETH flashloan.

Stolen Fund Movement

Through CertiK’s advanced monitoring system, we were able to detect three malicious addresses conducting this exploit.

pf5

The majority of losses were caused by EOA 0x7E39E3B3ff7ADef2613d5Cc49558EAB74B9a4202 which was responsible for approximately $11.6 million in stolen funds. The wallet was initially funded via FixedFloat and distributed stolen funds to three wallets, with one sending an on chain message to the Prisma Finance deployer claiming to be a white hat. Despite sending 1,850 ETH (~$$6.5m) to Tornado Cash, an on chain message was sent stating that funds would be moved to a safer place.

pf6

An additional two malicious wallets withdrew funds from Tornado Cash and conducted copy cat attacks leading to over $700,000 in losses.

Conclusion

Thankfully for the Prisma Finance users, the primary attacker responsible for the majority of the “loss” appears to intend to return the affected funds. This would mean that the amount of funds returned to targeted projects in 2024 stands at approximately $87 million on the premise that $10 million is returned to Prisma Finance which factors in a potential 10% bounty though, at time of writing, no bounty has been confirmed.