深入探讨重入攻击如何盗走Curve池7000万美元

《A Deep Dive Into How Curve Pool』s $ 70 Million Reentrancy Exploit Was Possible》

作者:CyberPunkMetalHead

原文翻译:Odaily 星球日报

近期的 Curve 池漏洞与我们在过去几年里看到的大多数加密货币黑客事件有所不同,因为与之前的许多漏洞不同,这一次并不直接与智能合约本身的漏洞有关,而是与它所使用的语言的底层编译器有关。

在这里,我们谈论的是 Vyper:一个面向智能合约的、具有 Pythonic 风格的编程语言,旨在与以太坊虚拟机(EVM)交互。我对此次漏洞的背后原因非常感兴趣,所以我决定深入研究。

随着这次漏洞的发展,每天的新闻头条都在报告新的数字。现在看来,情况终于得到了控制,但在此之前已经有超过 7000 万美元被盗。根据 LlamaRisk 的事后评估,截止到今天,有几个 DeFi 项目的池子也被黑客攻破,包括 PEGD 的 pETH/ETH: 1100 万美元;Metronome 的 msETH/ETH: 340 万美元;Alchemix 的 alETH/ETH: 2260 万美元;和 Curve DAO: 大约 2470 万美元。

这次漏洞被称为重入错误,它是在 Vyper 编程语言的某些版本上出现的,特别是 v 0.2.15、v 0.2.16 和 v 0.3.0 。因此,使用这些特定版本的 Vyper 的所有项目都可能成为攻击的目标。

什么是重入(reentrancy)? 

为了理解这次漏洞为什么会发生,我们首先需要了解什么是重入以及它是如何工作的。

如果一个函数在执行过程中可以被中断,并且在其之前的调用完成执行之前可以安全地再次被调用(「重新进入」),则称该函数为可重入的。可重入函数在硬件中断处理、递归等应用中都有使用。

为了使一个函数变得可重入,它需要满足以下条件:

– 它不能使用全局和静态数据。这只是一种约定,没有硬性的限制,但如果使用全局数据的函数被中断和重新启动,它可能会丢失信息。

– 它不应修改自己的代码。无论函数何时被中断,都应该能够以相同的方式执行。这可以管理,但通常不建议这样做。

– 它不应该调用其他非重入函数。重入不应与线程安全混淆,尽管它们紧密相关。一个函数可以是线程安全的,但仍然不是可重入的。为了避免混淆,重入只涉及到一个线程的执行。这是在没有多任务操作系统存在的时代的一个概念。

这里有一个实际的例子:

i = 5 

def non_reentrant_function():

 return i** 5 

def reentrant_function(number:int):

 return number** 5 

函数 non_reentrant_function:

– 这个函数没有参数。

– 它直接返回全局变量 i 的五次方。

– 所以当你调用这个函数时,它总是返回 5** 5 ,即 3125 。

函数 reentrant_function:

– 这个函数有一个参数 number,是整型。

– 它返回参数 number 的五次方。

– 这意味着你可以给这个函数传入任何整数,并得到这个数的五次方作为返回值。例如,如果你传入 2 ,它会返回 2 的 5 次方,即 32 。

值得注意的是,许多智能合约函数都不是可重入的,因为它们访问如钱包余额之类的全局信息。

什么是锁(Lock)?

锁本质上是一种线程同步机制,某个进程可以声称或「锁定」另一个进程。

最简单的锁类型被称为二进制信号量。这种锁为被锁定的数据提供独占访问。还有更复杂的锁类型,可以提供对读数据的共享访问。在编程中误用锁可能导致死锁或活锁,进程持续互相阻塞,状态不断改变但没有进展。

编程语言在后台使用锁来优雅地管理和共享多个子程序之间的状态更改。但是,某些语言,如 C# 和 Vyper 允许在代码中直接使用锁。

@nonreentrant(‘lock’)

def func():

 assert not self.locked, “locked”

 self.locked = True

 # Do stuff

 # Release the lock after finishing doing stuff

 raw_call(msg.sender, b””, value= 0)

 self.locked = False

 # More code here

在上面的例子中,我们希望确保如果 msg.sender(合同呼叫者)是另一个合同,它不会在执行时调用代码。如果在 raw_call() 下面还有更多的代码,而没有锁,msg.sender 可能会在我们的函数执行完毕之前调用上面的所有代码。

因此,在 Vyper 中,nonreentrant(『lock』) 装饰器是一种控制对函数的访问的机制,以防止调用者在它们完成运行之前反复执行智能合约函数。

在许多 DeFi 黑客事件中,通常都是合约开发者没有预见到的智能合约错误,一个聪明但恶意的利用者发现了某些函数或数据暴露的方式中的弱点。但这次的情况独特之处在于,Curve 的智能合约以及所有其他成为攻击受害者的池和项目在代码本身中都没有已知的漏洞。合同是稳固的。

nonreentrant(『lock』) 是存在的。

由于 Vyper 语言在处理重入锁的方式上出现了问题,导致了这个问题的发生。所以,合约创建者可能部署了看似合理的代码,但由于编译器没有正确处理锁,使得攻击者能够利用这个有缺陷的锁进行利用,导致合约行为出现意料之外的结果。

让我们看看真正受到重入攻击的合约。注意 @nonreentrant(『lock』) 修饰符吗?通常情况下,这应该可以防止重入,但实际上并未能防止。攻击者能够在函数返回结果之前反复调用 remove_liquidity()。

@nonreentrant(‘lock’)

def remove_liquidity(

   _burn_amount: uint 256,

   _min_amounts: uint 256 [N_COINS],

   _receiver: address = msg.sender

) -> uint 256 [N_COINS]:

   “””

   @notice Withdraw coins from the pool

   @dev Withdrawal amounts are based on current deposit ratios

   @param _burn_amount Quantity of LP tokens to burn in the withdrawal

   @param _min_amounts Minimum amounts of underlying coins to receive

   @param _receiver Address that receives the withdrawn coins

   @return List of amounts of coins that were withdrawn

   “””

   total_supply: uint 256 = self.totalSupply

   amounts: uint 256 [N_COINS] = empty(uint 256 [N_COINS])

   for i in range(N_COINS):

       old_balance: uint 256 = self.balances[i]

       value: uint 256 = old_balance * _burn_amount / total_supply

       assert value >= _min_amounts[i], “Withdrawal resulted in fewer coins than expected”

       self.balances[i] = old_balance – value

       amounts[i] = value

       if i == 0:

           raw_call(_receiver, b””, value=value)

       else:

           response: Bytes[ 32 ] = raw_call(

               self.coins[ 1 ],

               concat(

                   method_id(“transfer(address, uint 256)”),

                   convert(_receiver, bytes 32),

                   convert(value, bytes 32),

               ),

               max_outsize= 32,

           )

           if len(response) > 0:

               assert convert(response, bool)

   total_supply -= _burn_amount

   self.balanceOf[msg.sender] -= _burn_amount

   self.totalSupply = total_supply

   log Transfer(msg.sender, ZERO_ADDRESS, _burn_amount)

   log RemoveLiquidity(msg.sender, amounts, empty(uint 256 [N_COINS]), total_supply)

   return amounts

这是如何被利用的?

到目前为止,我们知道重入攻击是一种反复调用智能合约中的某个函数的方法。但这是如何导致资金被盗和在 Curve 攻击中损失 7000 万美元的呢?

注意智能合约末尾的 self.balanceOf[msg.sender] -= _burn_amount 吗?这告诉智能合约池中 msg.sender 的流动性,减去燃烧费。接下来的代码行为 message.sender 调用 transfer()。

因此,一个恶意合约可以在金额更新之前不断地调用提现,几乎让他们可以选择提取池中的所有流动性。

这样的攻击通常的流程是这样的:

– 易受攻击的合约有 10 个 eth。

– 攻击者调用存款并存入 1 个 eth。

– 攻击者调用提现 1 个 eth,此时提现函数执行一些检查:

– 攻击者的账户中是否有 1 个 eth?是的。

– 将 1 个 eth 转移到恶意合约。注意:合约的余额尚未更改,因为该函数仍在执行。

– 攻击者再次调用提现 1 个 eth。(重新入场)

– 攻击者的账户中是否有 1 个 eth?是的。

这将重复,直到池中没有更多的流动性。

Vyper 语言中的这个问题已经被修复,在 0.3.0 版本之后不再存在。如果您是开发人员,或使用 Vyper 的 Web3 组织,请确保立即更新您的版本。

来源链接

文章来源于互联网:深入探讨重入攻击如何盗走Curve池7000万美元

免责声明:

1.资讯内容不构成投资建议,投资者应独立决策井自行承担风险

2.本文版权归属原作所有,仅代表作者本人观点,不代表本站的观点或立场

上一篇 2023年8月7日 下午2:24
下一篇 2023年8月7日 下午5:41

相关推荐

  • Metis研究报告:Layer2的第一个去中心化排序器

    《研究报告:Metis》 作者:日月小楚 1 基本情况 1 在 layer2 中,排序器的作用是将成百上千的交易整理排序并压缩成一笔,然后交给 layer1 确认。可以说,是它实现了 layer2 的高性能和低手续费。但是,如果重要的东西却是中心化的,存在作恶或者单点故障的风险。可以说,layer2 排序器的去中心化势在必行。而 Metis 先行一步,27 …

    2023年12月6日
  • 全览RWA赛道:10大头部项目进展和20个早期项目概要

    作者:flowie,链捕手 尽管 RWA 一直难逃老故事割新轮的质疑,但从年初至今,RWA 接连不断有机构入场为其「添砖加瓦」,且 RWA 概念代币的涨势不俗,确实为低迷地加密市场带来了一些看点。 年初,我们在《Binance、高盛等大机构争相布局的 RWA,是 DeFi 下轮增长引擎还是昙花一现?》一文中简要分析过 RWA 的升温缘由。彼时Binance、…

    2023年7月12日
  • 律动晚报|某地址通过BananaGun交易SAVM获利677万美元;Saros将向社区空投约3.85亿枚SAROS

    精选要闻: 1.某地址通过 BananaGun 交易 SAVM 在 3 小时内赚取 677 万美元利润; 2.Saros 将向社区空投约 3.85 亿枚 SAROS,占其总供应量 3.85%; 3.比特币现货 ETF 第 5 日:净流出 1.31 亿美元,GBTC 流出 5.79 亿美元; 4.摩根大通:可能还有 15 亿美元将通过 GBTC 获利退出,将在…

    2024年1月19日
  • 数据:MakerDAO 联创近三个半月购入超 2 万枚 MKR

    ChainCatcher 消息,3 月 1 日,据链上观察员 @BitcoinEmber 数据监测,MakerDAO 联合创始人 Rune 自 2022 年 11 月 14 日以来,已通过 0x 购入约 20449 枚 MKR。 目前,Rune 地址持有 108,893 枚 MKR,价值约 1 亿美元,占 MKR 总量的 11%。Rune 购买 MKR 的资…

    2023年3月2日
  • 首个Ordinals开放平台TurtSat核心功能解析

    来源:TurtSat TurtSat 是一个由社区主导的基于 Ordinals 的开放平台,旨在成为 Ordinals 的 Gitcoin。任何人都可以通过 TurtSat 建立、捐赠和影响 Bitcoin Ordinals & BRC-20。TurtSat 将构建三个核心业务模块:Donation Protocol,索引服务以及 Ordinals …

    2023年5月22日
返回顶部