鸠占鹊巢: Furucombo 攻击事件分析

鸠占鹊巢: Furucombo 攻击事件分析 2021年02月27日去中心化投资组合平台Furucombo遭受了攻击并致使其平台下22名用户遭受了高达约15M美元的损失。在后续的分析中Furucombo | 派盾 | 慢雾发现该攻击利用了Furucombo平台操作的疏漏导致了平台合约转账权限的泄漏最终让攻击者成功转移了平台上用户的资产。在本文中我们将基于Furucombo协议、Proxy合约以及代币授权为切入点详细剖析此次事件的原理并展示完整的攻击过程。背景介绍Furucombo 简介Furucombo作为著名的DeFi资产投资组合工具其最大特点是为用户提供了无限DeFi协议组合集成功能与极其简化的操作页面。其中的平台功能包含了Aave平台的闪电贷功能以及各个DeFi交易所的换汇功能。Furucombo的出现极大简化了用户操作的复杂度为用户的投资策略的实施提供了极大的帮助。此次事件主要涉及到Furucombo-Proxy合约以及AaveLendingPoolV2合约。其中Furucombo-Proxy合约为Furucombo平台用于执行、验证用户请求的合约。而AaveLendingPoolV2合约则为Furucombo平台提供了闪电贷功能。为了添加AaveLendingPoolV2合约的闪电贷功能Furucombo平台首先需要将AaveLendingPoolV2合约的地址注册到Furucombo-Registry合约这里的Furucombo-Registry合约可以理解为Furucombo平台的合约白名单中。然后须由平台本身通过Furucombo-Proxy合约调用AaveLendingPoolV2合约的initialize函数进行初始化设置。为了更好地理解Furucombo平台的Proxy合约架构我们在下一部分对其做了详细的解释。Proxy 合约基于以太坊区块链的不可篡改性我们知道任何已经部署到链上的智能合约是无法得到更新的。但是在现实应用中漏洞修复以及协议的版本迭代往往都需要对智能合约进行不断的更新。为了拥有随时更新智能合约的特性Proxy合约应运而生。如下图在含Proxy合约的架构中一般涉及到三个主体用户、Proxy合约以及逻辑合约。在用户请求服务的情景下- 首先由用户向Proxy合约发起请求并指定需要调用的函数及相关参数。- 随之Proxy合约读取逻辑合约的地址并将用户提供的函数信息通过delegatecall传递给逻辑合约。- 最后如果用户请求的函数存在逻辑合约执行相关函数。反之fallback会被触发。在上图中值得注意的是当用户使用call调用Proxy合约的函数时所有读写操作都将作用于Proxy合约的Storage。不同的是当Proxy合约通过delegatecall调用逻辑合约中的函数时其函数所涉及到的所有读写操作也都将作用于Proxy合约类似于第三方库执行的context是在引用库的代码上。如上图所示虚线框中多有合约执行的读写操作都会作用于Proxy合约的Storage。由此可见在Proxy合约的架构中Proxy合约一般充当着数据库的作用而逻辑合约则提供了相应的逻辑代码。虽然在以太坊中已经部署上链的智能合约无法得到直接的更新但是在Proxy合约架构的帮助下平台可以通过更改Proxy合约中的逻辑合约地址来更新业务逻辑。代币授权Token Approval在此次事件中代币授权虽不是造成攻击的根源所在但是受害者不合理的代币授权行为却间接导致了该惨案的发生。换句话说如果用户能够有意识地控制其授予Furucombo平台的代币转账权限Token Approval便可以限制此次攻击所造成的损失。为了进一步了解什么是代币授权我们将在这一部分对其进行详细的介绍。在以太坊中除了以太币Ether之外还流通着各种各样的代币Tokens。ERC-20作为各式的代币标准之一是现存代币标准中最为流行的一种。关于ERC-20代币的授权问题主要涉及到其标准下的两个变量balanceOf 和 allowance 变量和两个函数approve 和 transferFrom 函数。balanceOf: 该变量记录所有地址的ERC20代币的数量。例balanceOf[A]的值表示A现在拥有的ERC20代币的数量。allowance: 该变量主要控制代币的交易及流通。例allowance[A][B]的值表示A允许B转移其代币的数量。approve(spender, amount): 调用者使用该函数去准许spender花费amount个自己的代币。transferFrom(sender, recipient, amount): 调用者(即approve函数中的spender)使用该函数向recipient转移amount数量的代币。该代币来源于sender且转移代币的数量受限于sender的实际拥有数量(balanceOf[sender])以及调用者实际被准许转移的数量(allowance[sender][spender])。在代币授权的流程中总共会涉及到三方代币拥有者token owner代币使用者token spender以及代币合约token contract。如下图所示代币合约会储存包括allowancebalanceOf等信息。与此同时代币拥有者和代币使用者都会通过函数调用与代币合约进行交互以达到代币授权转账等目的。在上图中我们展示了代币使用与授权的流程该流程具体包括以下三个步骤-在第一步中我们首先分别展示了Alice和Bob关于Token的基本信息。Alice和Bob分别拥有100个和0个Token。并且Alice此时并未授权给Bob任何她的Token。-在第二步中Alice调用了代币合约的approve函数授权100个token给Bob进行转账操作。在完成此操作后Alice和Bob的balanceOf并未发生任何变化但是allowance[Alice][Bob]却由0变为了100。这说明Bob已经有权限对Alice的100个Token进行转账操作。当然Alice也可以为Bob提供远超其余额的代币转账权限例如当balanceOf(Alice)100时Alice.approve(Bob, 500)的调用也可以成功。-在第三步中Bob调用了代币合约的transferFrom函数将Alice的80个Token转给了自己。通过观察balanceOf和allowance变量我们可以发现Bob的balanceOf变量来到了预期的80其allowance也降低到了相应的20。值得注意的是在Bob执行transferFrom的过程中如果balanceOf[Alice] 80或者allowance[Alice][Bob] 80Bob的转账操作将不会成功。攻击细节披露在本次攻击中其核心原因是Furucombo平台在Furucombo-Registry合约上注册AaveLendingPoolV2合约之后并未通过Furucombo-Proxy合约调用AaveLendingPoolV2合约的initialize函数进行初始化设置。这样的疏漏让攻击者抢先一步将自己的恶意合约部署为逻辑合约。因为攻击者的提前部署导致了Furucombo转账权限的泄漏攻击者从而可以转移Furucombo平台上的用户资产。由此可以看出当用户们向平台提供的转账权限越大那么其所遭受的损失也就越大。接下来我们将本次攻击分成两个阶段并对其进行详细的分析阶段一攻击者在部署其恶意合约上链后通过一个交易修改了Furucombo-Proxy合约的Storage为其后续的资产转移做了准备。如下图所示该交易的逻辑可以大致分为3个步骤。步骤1攻击者通过调用Furucombo-Proxy合约的batchExec函数向Furucombo平台发起了攻击。值得注意的是攻击者同时提供了两个关键的参数参数1: AaveLendingPoolV合约的地址参数2: 需要执行的函数信息即 initialize(address operator,bytes metadata)步骤2Furucombo-Proxy合约调用isValid(Furucombo-Proxy合约的内部函数)来验证攻击者提供的参数1。由于AaveLendingPoolV2合约已经作为功能之一被Furucombo平台注册于Furucombo-Registry合约所以验证通过。步骤3通过校验后Furucombo-Proxy合约使用delegatecall调用了AaveLendingPoolV2合约的initialize函数。值得注意的是在执行initialize函数时所有的读写操作都将发生于Furucombo-Proxy合约的Storage。但是由于Furucombo平台在注册AaveLendingPoolV2合约时未在AaveLendingPoolV2合约要求的位置(IMPLEMENTATION_SLOT 0x3608…2bbc)设置逻辑合约所以initialize函数被成功执行。在图中我们可以看到成功执行了initialize函数后攻击者合约的地址被成功部署到Furucombo-Proxy合约的Storage上为阶段二中的攻击做了铺垫。阶段二在当攻击者完成了阶段一的部署后便对Furucombo平台发动了多次攻击例如1、2、3。在下图中我们展示了攻击者在攻击交易中的具体逻辑其大致可以简化为5个步骤。步骤1攻击者通过调用Furucombo-Proxy合约的batchExec函数向Furucombo平台发起了攻击。在此阶段攻击者也同样提供了两个关键参数参数1: AaveLendingPoolV合约地址参数2: 需要执行的函数信息即 攻击者合约中的攻击函数0x12487d64步骤2该步骤与阶段一中的步骤2相同由Furucombo-Proxy合约对攻击者提供的参数1进行验证。步骤3通过校验后由于AaveLendingPoolV2合约中不含有攻击者请求的函数其fallback函数被触发。fallback会立即读取当前Storage(即Furucombo-Proxy合约的Storage)在IMPLEMENTATION_SLOT位置的合约地址即攻击者合约地址然后通过delegatecall向该合约传递攻击者的函数调用请求。步骤4AaveLendingPoolV2合约使用delegatecall调用了攻击者合约中的攻击函数。值得注意的是攻击函数的执行的环境仍在Furucombo-Proxy合约的Storage上。因此攻击函数在被调用的过程中其拥有了Furucombo-Proxy合约转账用户资产的权限。步骤5最后在攻击者提前设计的攻击函数中其针对给予了Furucombo平台转账权限的用户通过调用transferFrom函数将用户的资产转移进自己的账户来完成攻击。最后攻击者通过上述流程发起了多次攻击。如若平台没有及时对此次攻击做出反应并且用户持续给予Furucombo-Proxy合约代币转账权限那么攻击者便能持续收割授权平台转账权限的投资者们。平台措施在此次攻击中Furucombo平台在攻击发生之后的第一时间做出了反应。Furucombo平台为避免进一步扩大用户的损失其做出以下两个行为来保护用户的资产- 将AaveLendingPoolV2合约从Furucombo-Registry合约中除去deauthorized the relevant components。- 建议用户们取消对Furucombo-Proxy合约的资产转账授权remove approvals。然而经过我们在revoke.cash上对一些受害者地址的调查发现如下图部分受害者在Furucombo平台发出申明后并未及时撤回对Furucombo-Proxy合约的授权。由此我们可以推断用户对于资产授权问题可能依旧缺乏理解和重视。虽然此次攻击事件是由Furucombo平台的疏漏造成的但是我们不难发现用户对于大平台的资产转账授权行为为攻击者的获利提供了极大的便利。尤其是为不同平台提供了最大转账授权unlimited approval的用户们。我们在此建议用户在授权其代币给不同的平台时需要确认平台是否存在不合理的授权请求例要求用户提供最大代币转账权限同时仔细查阅平台的审计记录和理解平台的合约来规避恶意平台切忌盲目跟风投资。本文主要贡献者王达豹林子凌费俊杰吴斯韦参考Furucombo 博客 派盾 博客 慢雾 博客 Proxy Contract by openzeppelin