- Type: Exploit
- Network: Mainnet
- Total lost: ~$18MM in AMP and WETH
- Category: Reentrancy
- Exploited contracts:
- Attack transactions:
- Attack Block:: 13125071
- Date: Aug 30, 2021
- Reproduce:
forge test --match-contract Exploit_CreamFinance -vvv
- Add the contract to the universal interface registry
- Request a Flashloan
- Swap WETH for ETH
- Mint crETH tokens
- Enter Markets using crETH as collateral
- Borrow crAMP against crETH
- Deploy a minion contract
- Reenter borrowing crETH in the AMP receive hook
- The minion liquidates the main contract (commander).
- The liquidated amount is transferred from the minion to the commander.
- Selfdestruct the minion
- Swap ETH for WETH
- Repay the loan
The attacker reentered multiple pools borrowing WETH and AMP repeatedly over 17 txns.
This was possible mainly because the lending protocol transfers borrowed tokens before updating the internal accountancy values. In addition to this, as hookable tokens were used, the attacker was able to trigger a reentrant call to different contract which state was related with the first contract's.
function borrow(uint borrowAmount) external returns (uint) {
return borrowInternal(borrowAmount);
}
function borrowInternal(uint borrowAmount) internal nonReentrant returns (uint) {
...
return borrowFresh(msg.sender, borrowAmount);
}
function borrowFresh(address payable borrower, uint borrowAmount) internal returns (uint) {
...
doTransferOut(borrower, borrowAmount);
// We write the previously calculated values into storage
accountBorrows[borrower].principal = vars.accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = vars.totalBorrowsNew;
// We emit a Borrow event
emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew);
// We call the defense hook
comptroller.borrowVerify(address(this), borrower, borrowAmount);
return uint(Error.NO_ERROR);
}
Because the reentrancy mutex only protects functions that include that modifier, the attacker was able to call another contract borrowing undercollateralized amount.
- Respect the checks-effects-interactions pattern whenever it's possible taking into account that a reentrancy mutex does not protect against cross-contract attacks.