On April 30, 2022, Fuse of Rari Capital, a decentralized lending platform on the Ethereum blockchain, was hacked with a total loss of $80M. Multiple pools were hacked using flash loan to exploit a simple re-entrancy bug in the Compound code they were based on.
7 attack transactions drained tokens from various lending pools. Check the attacker's address to view all the attack transactions. The attack is consisted of two contracts (1, 2). In this post, we will discuss on this transaction used to drain Fuse Pool 127.
A functional PoC of the hack.
The lending protocol is based on a fork of Compound. Each lending pool in Compound is implemented through a cToken smart contract. A cToken is ERC20 compatible. For Fuse pools they are implemented with an identical fToken contract. For convenience, we will use the term cToken, but it actually refers to fToken and is not related to Compound.
To give a clearer image of how this protocol is used, we will think of a situation where you want to borrow ETH. To borrow ETH you must provide collateral. Suppose we want to provide some USDC as collateral. Then we should deposit that USDC in the protocol by minting fUSDC, and this liquidity we have provided will become the collateral. We can then borrow ETH from the protocol using the fETH contract.
The core of the borrow() function is implemented in borrowFresh(). The borrowFresh() implementation does not follow the check-effect-interaction pattern, thus making it vulnerable to re-entrancy. Basic re-entrancy attacks were mitigated by a simple update, but this bug can be further exploited by using the exitMarket() function of the Comptroller contract. The exitMarket() function can be called for any cToken. When there is no loan taken for the underlying assets of that cToken, the underlying assets will no longer be considered as a collateral, thus making those assets withdrawable.
The exploit developed by us is consisted of two smart contracts that reproduce the transaction which targets the Fuse Pool 127. We configure hardhat to fork the Ethereum network at #14684810, just before the hack happened.
The basic idea is to mint cUSDC to provide collateral, borrow ETH, call exitMarket() for cUSDC, withdraw the USDC from the pool, transfer ETH out. This is done by simply calling exitMarket() within the fallback function. The reason it works is because when the fallback function is called to receive the borrowed ETH, the loan is not updated on the protocol.
The two contracts used to reproduce the attack are the Launcher contract and the Exploit contract. The Launcher contract provides the initial liquidity via flash loan and retrieves the final profit. The Exploit contract exploits the re-entrancy bug to transfer the ETH to the Launcher and withdraw USDC to payback the flash loan.
To maximize the profit, we must maximize the amount of USDC we can provide as collateral. To do this, we borrow 150M USDC via flash loan from Balancer. After deploying the Exploit contract, the Launcher contract initiates the flash loan and sends this large amount of liquidity to the Exploit contract.
The Exploit contract mints cUSDC by first entering the market for cUSDC and depositing the 150M USDC received from the Launcher.
The cUSDC is then used to borrow the maximum amount of ETH (calculated by getCash()) from the fUSDC-127 (Fuse Pool 127) pool.
Within fallback function that is called when receiving the borrowed ETH, exitMarket() is called for cUSDC. Thus, after the fallback function returns the cUSDC is no longer considered as collateral and the exploit contract can redeem the underlying 150M USDC back from the pool.
After transferring out all the borrowed ETH, the exploit contract pays the flash loan with the redeemed USDC.
The same attack was repeated on many other pools causing catastrophic damage. Re-entrancy attacks have been here for a long time and proper mitigations are required.
We are PwnedNoMore, a white hat hacker DAO created by and for the best talents to protect our beloved crypto/Web3 world.