Reentrancy Attack on Cream Finance — Incident Analysis

Inspex
5 min readSep 1, 2021

--

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.

Related Addresses

Attack Steps

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

Code Analysis

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 borrow() function.

Borrowing tokens from Cream Finance can be done by calling the borrow() function. The borrow() function then executes the following underlying functions:

  • CCollateralCapErc20.borrow() calls CToken.borrowInternal()
  • CToken.borrowInternal() calls CToken.borrowFresh()
  • CToken.borrowFresh() calls doTransferOut()

The 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.

The 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 tokenReceived() function.

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 tokenReceived function.

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.

Since the 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.

Conclusion

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:
https://docs.google.com/spreadsheets/d/1u4MU4HFBx3dMc_Ogy09QzdAEGGhkZLVOPn64Pi2vTBc/edit#gid=0

About Inspex

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.

For any business inquiries, please contact us via Twitter, Telegram, contact@inspex.co

--

--

Inspex
Inspex

Written by Inspex

Cybersecurity professional service, specialized in blockchain and smart contract auditing https://twitter.com/InspexCo