
Editor's Note: This article comes fromPeckShield(ID:PeckShield), reprinted by Odaily with authorization.
Editor's Note: This article comes from
PeckShield security personnel actively followed up the bZx attack and found that this incident was an attack on the design of shared and composable liquidity among DeFi projects. Especially in DeFi projects with leveraged trading and lending functions, this problem will be more easily exploited.
Figure 1: Five Arbitrage Steps in bZx Hack
The attack details of the vulnerability are as follows:
The attack details of the vulnerability are as follows:
This attack event occurred at 2020-02-15 09:38:57 Beijing time (block height #9484688). The attacker's transaction information can be found on etherscan. This attack process can be divided into the following five steps:
Step 1: Get available funds with flash loan
Figure 2: Flashloan Borrowing From dYdX
image description
After the first step, the attacker’s assets in the following table have no benefits at this time:
Step 2: Hoard WBTC spot
Figure 3: WBTC Hoarding From Compound
image description
After this step, we can see that the asset controlled by the attacker has changed, but still no benefit at this point:
secondary title
Using bZx's leveraged trading function, short ETH to buy a large amount of WBTC. The specific steps are: the attacker deposits 1,300 ETH and calls the bZx leveraged trading function, namely the interface mintWithEther(), which will continue to call the interface marginTradeFromDeposit() internally. Next, the attacker exchanged 5,637.62 ETH obtained from bZx’s 5-fold leverage into 51.345576 WBTC through KyberSwap. Please note that shorting ETH here is 5 times borrowed. This transaction resulted in an increase of the WETH/WBTC exchange rate to 109.8, approximately 3 times the normal exchange rate (~38.5 WETH/WBTC).
Figure 4: Margin Pumping With bZx (and Kyber + Uniswap)
In order to complete this transaction, KyberSwap will basically query its reserves and find the best exchange rate. In the end, only Uniswap can provide such liquidity, so this transaction essentially drives up the price of WBTC in Uniswap by 3 times.
It should be noted that this step implements a security check logic inside the contract, but actually does not verify the lock value after the transaction. That is, when the attack occurred, this check was not enabled, and we will detail the problems in this contract in a later section.
After this step, we noticed the following changes regarding the assets controlled by the hackers. Still no profit after this step, though.
image description
Figure 5: WBTC Dumping With Uniswap
Step 5: Flash loan repayment
secondary title
Step 5: Flash loan repayment
After this step, we recalculated the following asset details. The results show that the attacker obtained 71 ETH through this attack, plus these two locked positions: Compound (+5,500weth/-112WBTC) and bZx (-4,337WETH/+51WBTC). The lock position of bZx is in default, and the lock position of Compound is profitable. Apparently, after the attack, the attacker started to repay the Compound debt (112BTC) to redeem the mortgaged 5,500 WETH. Since the bZx lockup is already in default, the attacker is no longer interested.
Referring to the average market price of 1WBTC=38.5WETH (1WETH=0.025BTC), if the attacker buys 112 WBTC at the market price, it will cost about 4,300 ETH. The 112 WBTC is used to pay off Compond’s debt and get back the collateral of 5,500 ETH, so the final attacker’s total profit is 71 WETH +5,500 WETH -4,300 ETH=1,271 ETH, totaling about $355,880 (the current ETH price is $280).
secondary title
Hard core analysis: bZx can avoid risky code logic defects
From the previous steps implemented by the attacker in the contract, it can be seen that the core cause of the problem is that in the third step, marginTradeFromDeposit() is called to short ETH/WBTC transactions by borrowing 1,300 ETH and adding 5 times leverage, so we further review The contract code found that this is an "avoidable arbitrage opportunity", but because of the logic error in the code, the code logic that can be used to avoid risks did not take effect. The specific code tracking is as follows:
First, marginTradeFromDeposit( ) calls _borrowTokenAndUse( ). Here, since the deposited assets are used for leveraged trading, the fourth parameter is true (line 840).
In _borrowTokenAndUse( ), when amountIsADeposit is true, call _getBorrowAmountAndRate( ) and store the borrowAmount in sentAmounts[1] (line 1,348).
At line 1,355, sentAmounts[6] is set to sentAmounts[1] and _borrowTokenAndUseFinal( ) is called at line 1,370
Enter the takeOrderFromiToken( ) function of bZxContract via the IBZx interface.
bZxContract belongs to another contract iTokens_loanOpeningFunctions So we continue to analyze the contract code and find a key logical judgment in the function:
In line 148, bZx actually tries to use shouldLiquidate( ) of the oracle contract to check whether the position of this leveraged transaction is healthy. However, since the first condition (lines 146-147) is already true, execution continues, ignoring the logical judgment of shouldLiquidate().