DeFi协议Akropolis漏洞详解:黑客复现“经典重入攻击”掳走203万DAI

  • A+
所属分类:安全新闻

DeFi协议Akropolis漏洞详解:黑客复现“经典重入攻击”掳走203万DAI

DeFi协议Akropolis漏洞详解:黑客复现“经典重入攻击”掳走203万DAI


昨天晚间19时50分,DeFi 协议 Akropolis 遭到了黑客攻击。


区块链安全公司 PeckShield(派盾)安全人员迅速定位到问题在于,Akropolis  项目的 SavingsModule 合约在处理用户存储资产时存在某种缺陷,黑客利用此缺陷连续实施了17次重入攻击,导致其 YCurve 和 sUSD 资金池损失了203万枚 DAI。 


技术概要:


本次攻击的原因如下:

1)合约没有对用户存储的 Token 进行白名单校验

2)关键的 deposit 函数没有对重入攻击的保护

简单而言:黑客利用 Akropolis 项目存在的存储资产校验缺陷,向合约发起连续多次的重入攻击,致使 Akropolis 合约在没有新资产注入的情况下,凭空增发了大量的 pooltokens,进而再利用这些 pooltokens 从 YCurve 和 sUSD 池子中提取 DAI,最终导致项目合约损失了203万枚 DAI。


攻击过程详解:


攻击流程复现:


我们通过分析黑客实施攻击的交易哈希(0xe1f375a47172b5612d96496a4599247049f07c9a7d518929fbe296b0c281e04d)发现,攻击来自于一个恶意的 ERC20 合约地址(0xe2307837524Db8961C4541f943598654240bd62f)


这个恶意合约实现了一个钩子函数,使得函数在 transferFrom() (function signature: 0x23b872dd)被调用的时候会被执行。


攻击者先是调用 SavingsModule.sol (https://github.com/akropolisio/delphi/blob/release-1.0/contracts/modules/savings/SavingsModule.sol#L217-L277) 中的 deposit() 函数,并将自己编写的位于 0xe230 开头的恶意合约作为要存储的代币地址传入。当其恶意代币的 transferFrom() 函数被调用时,其钩子函数会再次调用 deposit() 函数并存入真实的 DAI 资产


由于 pooltokens 增发的量通过代币 deposit 前后余额的差值得出。所以第二次 deposit 的真实的 DAI 资产会被计算两次用于铸造 pooltokens。第一次是在恶意合约 0xe230 存储的时候,第二次是在 DAI 存储的时候。也就是说,如果第二次存储的时候存入了 25K DAI, 那么由于重入攻击,总的铸造的 pooltokens 将会是双倍,也就是 50K DAI 


DeFi协议Akropolis漏洞详解:黑客复现“经典重入攻击”掳走203万DAI


以此类推,黑客总共发起 17次重入攻击并获得了总共 2,030,841.0177个DAI 资产。


值得注意的是,在攻击的最开始,攻击者还使用了 dYdX 的闪贷功能(https://etherscan.io/tx/0xddf8c15880a20efa0f3964207d345ff71fbb9400032b5d33b9346876bd131dc2)。


核心漏洞详解:


接下来,我们分析下存在漏洞的代币存储逻辑。Akropolis 的用户可以将代币存储入Delphi Savings Pools,而资金池会铸造相应的 pooltokens 给用户。核心逻辑在SavingsModule::deposit()(1,944行)。


DeFi协议Akropolis漏洞详解:黑客复现“经典重入攻击”掳走203万DAI


第一步:攻击者调用 deposit() 函数并提供 _tokens 参数。这个函数在进一步调用 depositToProtocol(_protocol, _tokens, _dnAmounts) 前后会计算代币的余额,并通过代币余额的变化来决定将要铸造的 poolTokens 数目(第1,970行)。而 depositToProtocol() 函数会调用目标代币的 safeTransferFrom()函数来进行代币的转账(第2,004行)。然而 deposit() 函数没有对重入攻击进行检测,也没有检查存入的代币是否为恶意代币;


第二步:在恶意代币的 transferFrom() 函数被调用的时候,触发钩子函数,从而再次调用 deposit() 函数;


第三步:因为第二次调用 deposit() 函数的时候攻击者存入了真正的 DAI 代币使得池子的代币余额发生变化,所以攻击者可以获得资金池铸造的 poolTokens;


第四步:当第二次 deposit() 函数调用结束的时候,代码执行流程将返回第一次存储代币调用 depositToProtocol() 函数的上下文。这个时候,代币余额变化将被再次计算。此时代币余额的变化和第二次调用 deposit() 函数代币余额变化一样。因此攻击者可以再次获得相应数目的 poolTokens。


被盗资产情况:


这次攻击的被盗资产目前被存储在钱包[0x9f26](https://etherscan.io/address/0x9f26ae5cd245bfeeb5926d61497550f79d9c6c1c)中。PeckShield 旗下数字资产追踪平台 CoinHolmes 正在对该地址做全方位监控,并对其资金流向做进一步的锁定分析和追踪,以便协助项目方挽回被盗资产。为业内领先的区块链安


推荐阅读:

PeckShield:硬核技术解析,bZx协议遭黑客漏洞攻击始末

PeckShield:DeFi平台Opyn智能合约漏洞详解——攻击者空手套白狼!

PeckShield:DeFi平台Balancer遭黑客攻击全过程技术拆解

PeckShield:bZx协议再遭黑客“二连击”背后的技术命门

0x协议漏洞原理剖析:恶意挂单可扰乱正常交易秩序

Uniswap和Lendf.Me遭攻击始末:DeFi乐高组合下的“多米诺”式崩塌

AirSwap智能合约漏洞详解:用户资产可被攻击者恶意吃单?


DeFi协议Akropolis漏洞详解:黑客复现“经典重入攻击”掳走203万DAI

本文始发于微信公众号(PeckShield):DeFi协议Akropolis漏洞详解:黑客复现“经典重入攻击”掳走203万DAI

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: