Reentrancy Attack on Cream Finance — Incident Analysis
Starting from Aug 30, 2021, 05:44:47 AM UTC, Cream Finance was affected by a reentrancy attack based on the implementation of $AMP token. In this article, we will describe the technical details of this attack step-by-step.
- Attacker’s contract #1 (C#1): https://etherscan.io/address/0x2e95b91fa678b47660aba811b74a28ca1f4ed111 (example code)
- Attacker’s contract #2 (C#2): https://etherscan.io/address/0x256aabfcc66bda3e63cae35166e7ac339cc59bf3 (example code)
- Attacker’s wallet: https://etherscan.io/address/0xce1f4b4f17224ec6df16eeb1e3e5321c54ff6ede
We’ve identified 2 patterns of attack. The steps of each pattern are as follows.
Pattern 1 (example transaction)
- Flash loan $WETH from Uniswap V2
- Deposit borrowed $WETH into Cream Finance as a collateral, mint $crETH
- Borrow $AMP from Cream Finance with reentrancy call to borrow $ETH
- Swap some $AMP for $WETH
- Pay borrowed $WETH flashloan
- Transfer profit to wallet
Pattern 2 (example transaction)
- Use contract #1 to flash loan $WETH from Uniswap V2
- Deposit borrowed $WETH from contract #1 to Cream Finance as a collateral
- Use contract #1 to borrow $AMP from Cream Finance with reentrancy call to borrow $ETH
- Transfer $AMP from contract #1 to contract #2
- Use contract #2 to paid $AMP to liquidate borrow loan of contract #1 and get $crETH back
- Use contract #2 to redeem $crETH for $ETH in Cream Finance
- Transfer all $ETH from contract #2 to contract #1
- Wrap all $ETH in contract #1 and paid borrowed $WETH flashloan
- Transfer all profit from contract #1 to wallet
We will explain the attack steps in detail by using the attack pattern 2 in the following transaction: https://etherscan.io/tx/0xd7ec3046ec75efbd04b3eea8752a8a6373a92c0dd813d08b655661054d3239c5
1. Attacker flash loaned 1,000 $WETH from Uniswap V2, unwrapped $WETH to $ETH, deposited $ETH as a collateral, and 48,344.60728628 $crETH was minted to the attacker contract #1 (C#1 = 0; C#2 = 0)
2. Borrowed 38,960,000 $AMP from Cream Finance, then borrowed 710 $ETH to attacker contract #1 with a reentrancy attack (C#1 = 38.96 M $AMP, 710 $ETH; C#2 = 0)
3. Transferred borrowed 19,480,000 $AMP from attacker contract #1 to attacker contract #2 (C#1 = 19.48 M $AMP, 710 $ETH; C#2 = 19.48 M $AMP)
4. Attacker contract #2 repaid 19,480,000 $AMP to liquidate attacker contract #1 loan, got 17,533.7532 $crETH back (C#1 = 19.48 M $AMP, 710 $ETH; C#2 = 17,533.7532 $crETH)
5. Attacker contract #2 redeemed 17,533.7532 $crETH, got 362.682710321137400481 $ETH back (C#1 = 19.48 M $AMP, 710 $ETH; C#2 ≈ 362.68 $crETH)
6. Attacker contract #2 transferred 362.682710321137400481 $ETH to attacker contract #1, wrapped all $ETH to $WETH, and repaid borrowed flash loans $WETH to Uniswap V2
7. Attacker gained 19,480,000 $AMP and 69.582710321137400481 $WETH profit, and transferred them to attacker’s wallet
We begun an initial incident analysis by analyzing the attack-related transactions. The first indicator we found and used as the analysis’s starting point is the recursive execution of the
Borrowing tokens from Cream Finance can be done by calling the
borrow() function. The
borrow() function then executes the following underlying functions:
doTransferOut() function is executed subsequently from
borrow() function and updates the state variables.
To accomplish the transferring, the
doTransferOut() function relies on the
transfer() function of
AMP token to transfer $AMP to the borrower.
transfer function execution takes required parameters (
to) which specified by borrower address, and again, executes its underlying functions. The execution eventually drops in the
_callPostTransferHooks() function which has a potential reentrancy vector on
By specifying the attacker-controlled contract address on
recipientImplementation that derived from
_to parameter, the attacker makes use of
tokenReceived function for reentrancy attack. The only attack condition here is to create a contract that contains
For the attack, the attacker hijacked the control flow with this technique and reentered the
borrow()function to borrow $ETH from Cream Finance, bypassing the lending agreement specified on the contract.
doTransferOut() function was called before updating the state variables and can make a reentrancy call, the attacker would not have to pay the borrowed debt to Cream Finance for the first time he borrowed the $AMP tokens.
In summary, by repeating the attack in 16 transactions. The attacker gained 419,709,548.31 $AMP and 1,308.09 $WETH in total from Cream Finance. For the complete details, we have summarized the information for each transaction in the sheet below:
Inspex is formed by a team of cybersecurity experts highly experienced in various fields of cybersecurity. We provide blockchain and smart contract professional services at the highest quality to enhance the security of our clients and the overall blockchain ecosystem.