On October 27, 2021, Cream Finance suffered another attack loss of approximately US$130 million. The SlowMist security team immediately intervened in the analysis and shared its brief analysis as follows.
The root cause of the attack
The contract of this attack is to use the flaws in the Cream lending pool to obtain the price of collateral, and malicious manipulation increases the price of its collateral, allowing the attacker to borrow more tokens from the Cream lending pool.
First, the attacker borrowed 500 million DAI from DssFlash flashloan, and then mortgaged the 500 million DAI to the yearn yDAI pool to obtain about 450 million yDAI certificates.
The attacker then added the obtained yDAI tokens to Curve’s yDAI/yUSDC/yUSDT/yTUSD pool for single-currency liquidity addition to obtain corresponding liquidity certificates. Then the attacker mortgaged the obtained certificates to the yUSD pool to obtain yUSD certificates in preparation for subsequent mortgages in the Cream crYUSD lending pool.
After that, the attacker began to mortgage Cream’s crYUSD lending pool to obtain yUSD certificates. In order to expand its mortgage scale, the attacker loaned approximately 524,000 WETH from AAVE flashLoan and mortgaged them to crETH pool of Cream.
The attacker mortgaged a large amount of ETH in the crETH pool to make it have enough borrowing capacity to lend all the yUSD in the crYUSD pool and re-pledged it to the crYUSD pool, and then expand in the form of leverage through revolving loans in the crYUSD pool. The yUSD collateralized scale in the crYUSD pool is used to prepare for subsequent price manipulation and profitability.
Subsequently, in order to obtain yDAI/yUSDC/yUSDT/yTUSD 4Pool certificates to manipulate the price, the attacker used about 1,873 ETH to exchange about 7.45 million USDC from Uniswap V3, and converted it into about 3.38 million DUSD tokens through Curve 3Pool.
Next, the attacker used the obtained DUSD tokens to redeem the yDAI/yUSDC/yUSDT/yTUSD 4Pool certificates from YVaultPeak, and used these certificates to retrieve yDAI/yUSDC/yUSDT/yTUSD tokens from the yUSD pool.
Then the attacker began to perform the key operations. It transferred about 8.43 million yDAI/yUSDC/yUSDT/yTUSD tokens directly back to the yUSD pool. Since they were not mortgaged through normal mortgage operations, these 8.43 million yDAI/yUSDC The /yUSDT/yTUSD tokens are not accounted separately, but are directly distributed to the holders of yDAI/yUSDC/yUSDT/yTUSD vouchers, which is equivalent to directly raising the price of their shares.
In crToken, the price of its collateral was maliciously increased, so the attacker’s mortgage of a large amount of yUSD can make it lend more funds, and finally the attacker lent all the other 15 pools of Cream. Next, we follow up the specific borrowing logic in Cream’s crToken lending pool.
From the cToken contract, we can see that the main lending check is in the borrowAllowed function:
Following up the borrowAllowed function, we can see that in line 427, it will check the sum of the asset value of all cTokens and the sum of the lending asset values corresponding to the account in the real-time state according to the getHypotheticalAccountLiquidityInternal function, and compare the asset value of cToken with The sum of the borrowed token value is used to determine whether the user can continue to borrow.
Following up the getHypotheticalAccountLiquidityInternal function, we can find that the value of the collateral is obtained from line 886 of `oracle.getUnderlyingPrice`
Following the getUnderlyingPrice function of the oracle, and we can easily find that it will get the price through the getYvTokenPrice function of 150 lines of the token.
Continue to follow up on the getYvTokenPrice function. Since `yvTokenInfo.version` is V2, the price will be obtained through the pricePerShare function of yVault.
Following up on pricePerShare, we can find that it directly returns _shareValue as the price, and _shareValue calculates the price of a single share by dividing _totalAssets by the total number of shares in the contract (self.totalSupply). Therefore, the attacker only needs to manipulate _totalAssets to raise it to increase the price of a single share, thereby making the value of the attacker’s collateral higher to lend more other tokens.
We can check how _totalAssets is obtained. From line 772, we can clearly see that _totalAssets is the number of yDAI/yUSDC/yUSDT/yTUSD tokens directly taken from the current contract, and the amount of assets pledged in the strategy pool Added together. Therefore, by directly transferring yDAI/yUSDC/yUSDT/yTUSD tokens to the yUSD contract, the attacker can increase the share price and complete profit.
Through Ethtx.info, you can clearly see the changes before and after pricePerShare:
In the end, the attacker returned the flashloan and took a profit, and left.
This attack is a typical price manipulation using flashloan. Because Cream’s lending pool directly uses its pricePerShare interface when obtaining the share price of the yUSD pool, and this interface is added by the balance of the contract’s collateral and the amount of collateral assets in the strategy pool. Divide the total number of shares to calculate the price of a single share. Therefore, the user can easily increase the price of a single share by directly transferring the collateral to yUSD, and finally allows the collateral in the Cream loan pool to lend more funds.