硬核技术解析 | bZx协议遭黑客漏洞攻击始末
Figure 1: Five Arbitrage Steps in bZx Hack
漏洞的攻击细节如下:
此攻击事件发生在北京时间 2020-02-15 09:38:57(块高度#9484688)。攻击者 的transaction 信息可以在 etherscan 上查到。 此攻击过程可以分为以下五个步骤:
Figure 2: Flashloan Borrowing From dYdX
当第一步操作过后,如下表中攻击者资产,此时并没有收益:
Figure 3: WBTC Hoarding From Compound
在此步骤操作后,我们可以看到关于攻击者控制的资产发生了改变,但此时仍然没有获益:
Figure 4: Margin Pumping With bZx (and Kyber + Uniswap)
应该注意的是, 这步操作在合约内部实现有个安全检查逻辑,但是实际上在交易之后并没有验证锁仓值。 也就是说,当攻击发生时,此检查没有启用,我们在后面会有一节详细介绍此合约中的问题。
在这一步之后,我们注意到关于黑客控制的资产有以下改变。不过,在这一步之后仍然没有获利。
Figure 5: WBTC Dumping With Uniswap
参考 1WBTC=38.5WETH(1WETH=0.025BTC)的平均市场价格,若攻击者以市场价格购入112 WBTC花费约需4,300个 ETH。此112 WBTC 用以清偿 Compond 债务并取回抵押品5,500 ETH,则 最终攻击者总共获利为 71 WETH +5,500 WETH -4,300 ETH=1,271 ETH,合计大约$355,880(当前ETH价格$280)。
硬核解析:bZx 可规避风险代码逻辑缺陷
首先是 marginTradeFromDeposit( ) 调用 _borrowTokenAndUse( ),此处由于是以存入的资产作杠杆交易,第四个参数为 true(第840行)。
在 _borrowTokenAndUse( ) 里,当 amountIsADeposit 为 true 时,调用 _getBorrowAmountAndRate( ) 并且将 borrowAmount 存入 sentAmounts[1] (第1,348行)。
在 1,355 行,sentAmounts[6] 被设置为 sentAmounts[1] 并且于第1,370行调用 _borrowTokenAndUseFinal( )
经由 IBZx interface 进入 bZxContract 的 takeOrderFromiToken( ) 函数。
bZxContract属于另一个合约 iTokens_loanOpeningFunctions 于是我们我们继续分析合约代码,在函数中发现有一个关键的逻辑判断:
在第148行,bZx 事实上尝试利用 oracle 合约的 shouldLiquidate( ) 检查这个杠杆交易的仓位是否健康。然而,因为第一个条件(第146~147 行)已经为 true,则继续执行,而忽略了 shouldLiquidate() 的逻辑判断。
事实上,在合约 BZxOracle 的 shouldLiquidate( ) 中实现了对 getCurrentMarginAmount( ) <= loanOrder.maintenanceMarginAmount 判断,如果执行到 shouldLiquidate( ) 就可以有效避免这个攻击的发生。
如前所述,这是一次很有意思的攻击,它结合了各种有趣的特性,如贷款、杠杆交易和拉高价格等。之所以可能发生这种攻击,是因为当前项目共享可组合流动性的设计。 特别是,5倍杠杆交易允许用户以相对较低的成本借入大量代币,加上 DeFi 项目间共享的流动性,导致交易价格更容易被操控。