Akropolis黑客攻击事件:根本原因分析
此事件是由于协议中的错误而导致的,该错误没有(1)验证支持的代币,以及(2)对存款逻辑实施可重入保护。这种利用导致大量的池代币没有得到有价值资产的支持然后赎回这些铸造的池代币,从受影响的YCurve和sUSD池中抽走约200万DAI。
攻击细节详解
我们从黑客背后的交易开始分析:0xe1f…e04d。此黑客是从类似ERC20的恶意合约(位于0xe230)初始化的。此恶意合约实现了一个钩子,只要调用transferFrom()(函数签名:0x23b872dd),该钩子就会被调用。
有了这个准备,黑客首先通过以准备好的0xe230合约作为令牌在SavingsModule.sol中调用deposit()来发起攻击,并在调用令牌的transferFrom()函数时,调用钩子函数,该钩子函数实质上重新 输入相同的deposit()但具有真实的DAI资产(0x6b17)。但是计算铸造的代币池数量的方法取决于存款前后的余额差(第234-236行)。事实证明,具有真实DAI资产的第二笔存款将被有效计数两次,以计算铸造的代币池数量:一个用于0xe230存款,另一个用于DAI存款。换言之,如果第二笔存款转移了2.5万DAI,则造币池代币的总数将翻倍,估值为5万DAI(因为可重入)。
我们的分析表明,攻击者发起了17次交易攻击,一共获得了2,030,841.0177个DAI。请注意,在一开始,就借用了dYdX快速借贷来启动一系列交易攻击。
存款逻辑
接下来,我们详细阐述脆弱存款逻辑。任何Akropolis用户都可以将代币存入Delphi储蓄池,该池将为用户生成相应的代币池。核心逻辑是用SavingsModule::deposit()编写的(第1944行),如下图所示。
*SavingsModule中的脆弱存款逻辑*
步骤1:攻击者使用指定的_tokens作为输入来调用deposit()。此函数计算存款函数depositToProtocol(_protocol,_tokens,_dnAmounts)之前和之后的代币余额。然后它使用余额更改来铸造poolTokens(第1970行)。在计算余额变化之间,depositToProtocol()函数在目标代币上调用safeTransferFrom()函数以执行实际的代币传输(第2004行)。但是,没有对deposit()函数进行重新入场检查,也没有对可能是精心设计和恶意的存入代币进行有效性检查。
步骤2:攻击者在调用其transferFrom()函数来调用钩子例程时再次重新输入deposit()函数。
步骤3:由于这是第二次存款,该池将向攻击者铸造poolToken,因为实际的DAI资产已转移到协议中并更改余额。
第4步:第二次存款完成后,它将在depositToProtocol()中返回到第一次存款的证明,然后再次计算余额变化!事实证明,余额差额与第二笔存款完全相同。因此向攻击者铸造了相同数量的poolToken。
最终被盗资金目前存放在该钱包中:0x9f26。
Scan QR code with WeChat