智能合约安全:闪电贷攻击

智能合约安全:闪电贷攻击

闪电贷攻击

本文是视频 详解闪电贷攻击手段@BuidlerDAO 的学习笔记。

1. 什么是闪电贷?闪电贷与区块链安全讲解

闪电贷就是在单笔交易中贷出借款人的金额,在交易结束时,借款人必须偿还不少于贷款金额的数目。如果借款人做不到,则交易就会自动回滚,就像贷款根本没有发生一样。主要包括三点特征:

  1. ​闪电贷是一种无抵押贷款;
  2. 所有闪电贷都是通过区块链上的智能合约完成的;
  3. 贷款过程是即时的,一个区块内完成贷款和还款。

Uniswap Swap() 闪电贷

V2 核心合约 Pair(即供给池)的 swap 函数实现了 calldata 调用,并且可以让用户先兑后还。假设有一个支持 DAI/ETH 的 Pair(供给池),一开始 ETH 会从 Pair 借出到外部套利合约,然后 Pair 会去调用套利合约实现的 uniswapV2Call 接口。uniswapV2Call 中可以完成一系列的“闪电”业务,之后 uniswapV2Call 再将相应的 ETH 或者 DAI 返还给 Pair。Pair 对 uniswapV2Call 调用结束后,会进行最终的账目核对。如果 Pair 没有收到足够的 ETH 或者 DAI,那么整个交易都将回滚。因此对于套利者,借出一笔 ETH 后,必须在该原子交易结束前再将 ETH 或 DAI 返还(并付上 0.3% 的费率),才能保证整个闪电兑换的成功。

闪电贷交互分为三部分:

  • 借贷
    • 兑换数量和储备校验
    • 检查:接收方地址(to)不能是 tokenA 和 tokenB 的地址
    • 将 tokenA 和 tokenB 贷出
  • 调用目标合约接口
    • IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);​ 调用闪电贷接口完成业务逻辑
  • 还款
    • 校验还款金额 >= 贷款 + 费用
    • K 值校验
    • 更新储备金

Aave flashLoan() 闪电贷

  • 函数参数:function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes memory _apras) ...
    • 参数:接收地址、Token 地址、数量、接收方接口参数(跨合约调用时传入参数)
  • 函数流程
    • 查询可借贷的流动性
    • 确保有足够的数量能够借出
    • 计算费用:借贷费用 + 协议费用
    • 代币转给接收地址
    • 跨合约调用目标闪电贷接口
    • 检查借贷+费用是否还清
    • 更新合约状态

闪电贷与区块链安全

闪电贷本身的存在是没有漏洞的,但使用者可以以极低的成本撬动巨量资金,在多个协议之间进行价格操纵等。2022 年上半年使用闪电贷进行黑客攻击的案例总计 21 次,占比 26.6%,涉及金额高达 3 亿 3291 万美元。其中上半年影响较大的攻击手段有闪电贷加治理攻击,闪电贷加价格操纵攻击,闪电贷加重入攻击等。

闪电贷攻击案例一

2021 年 6 月 23 日,基于币安智能链(BSC)的稳定币交易平台 Nerve Finance 受到闪电贷加逻辑漏洞攻击,损失高达 460 万美元。

其原因在于紧急提取功能emergencyBurn​ 没有销毁存放在合约中的 NRV-LP 代币。

攻击过程:

  1. 闪电贷 242W BUSD 并在稳定币 Nerve 3Pool 中兑换为 241 万枚 3NVR-LP;
  2. 攻击者将 241 万枚 3NRV-LP 添加至 ElevenNeverSellVault 合约后获得 241 万枚 11Pool3 Nerve。紧急取出在 ElevenNeverSellVault 合约添加的 241 万枚 3NRV-LP;
  3. 重复过程 2;
  4. 将通过攻击的 3NRC-LP 兑换为 BUSD,偿还闪电贷并将获利的资金转给钱包。

闪电贷攻击案例二

2022 年 4 月 17 日,算法稳定币项目 Beanstalk Farms 遭到闪电贷加治理攻击,黑客获利 7600 万美元,协议损失 8200 万美元。

攻击过程大致如下:准备阶段:由于在 BEAN 合约中,所有治理行动都有 1 天的延迟,即提案后 1 天才能开始投票,所以攻击者实际上是在前一天提出两个治理提案,其中,第一个提案(提议18)提取合约中所有的钱。下一个提案(提议19)将价值 25 万美元的 $BEAN 发送到乌克兰的捐款地址。该攻击主要利用了投票合约中的投票是通过代币余额查询和校验的,然后调用 emergencyCommit​ 进行紧急提交以执行该提议。

攻击阶段:

  1. 攻击者从 Synapse 协议桥(最初来自 Tornado.cash)获取初始资金;
  2. 利用闪电贷获得价值超 10 亿资金:从 Aave 获得 3.5 亿 DAI,5 亿 USDC 和 1.5 亿 USDT;从 Uniswap v2 获得 3200 万 BEAN;从 SushiSwap 获得 1160 万 LUSD;
  3. 这些代币被用来为 Curve 池子增加流动性,临时获得巨额的提案代币,保证了提案不需要其他人投票也能通过。
    • Curve.fi DAI/USDC/USDT 交易池新增 DAI、USDC、USDT,最终获得了 979,691,328 3Crv流动性代币
    • 其中 15,000,000 3Crv 兑换 15,251,318 LUSD
    • 将 964,691,328 个 3Crv 代币兑换成 795,425,740 BEAN3CRV-f 进行投票,并为 32,100,950 BEAN 和 26,894,383 LUSD 增加流动性,获得 58,924,887 BEANLUSD-f 流动性代币。

本案例中是通过闪电贷获得大量的治理代币发起攻击。

闪电贷攻击案例三

2022 年 6 月 22 日,PandoraDAO 遭遇闪电贷加价格操纵攻击,造成了约 12.8 万美元的损失。

其原因在于查询价格的预言机设计存在问题。

攻击步骤:

  1. 通过从 USDT-PCD 合约中闪贷大量 USDT 来准备价格操纵;
  2. 此时数量发生变化,价格也发生了变化,调用 shouchan​ 函数以极低价格购买 PCD 代币,并锁仓;
  3. 解锁,并通过 PancakeSwap 卖出。

本案例中是直接通过闪电贷获得代币发起攻击。

闪电贷攻击案例四

​2022 年 3 月 15 日,OMNI 协议遭受闪电贷加重入攻击,黑客共获取了 362 万枚 USDC,169 万枚 xDAI,16 枚 WBTC,24 枚 WETH,价值超过 600 万美元。

其原因在于:

  1. ERC667BridgeToken 中的 transfer​ 函数中的钩子 callafterTransfer​ 实现了调用目标合约的功能(使得能够多次发起调用);
  2. borrow​ 函数中并未按照检查-生效-交互模式实现逻辑,并且没有防重入修饰器修饰该函数(可以实现回调);

攻击流程:

  1. 从 SushiSwap 中进行闪电贷,209 万 USDC,337 万 WXDAI;
  2. 随后,攻击者创建了多个合约同时存款借贷然后进行重入获利,可以查看其中一个地址 0xbE8fe2aE087aeCcB1E46EF206368421c9212637B​。

2. 如何减缓闪电贷攻击?

1. 要求关键交易跨越两个区块

如果一个资本密集型交易需要跨越至少两个区块,用户需要至少在两个区块时间段取出贷款,那么闪电贷攻击将会失效。但是要达到这一效果,两个区块之间用户价值必须锁定,以防止其偿还贷款。

2. 时间加权平均定价

在价格操纵案例中,建议使用时间加权平均价格(TWAP)来跨多个区块计算流动性池中的价格。因为整个攻击交易序列需要在同一个区块内处理,但如果不操纵整个区块链就无法操纵 TWAP,从而可以避免闪电贷导致的瞬时价格异常。

3. 更高频率的价格更新机制

同样在价格操作案例中,可以适当增加流动性池向预言机查询并更新价格的频率,随着更新次数的增加,池中代币的价格会更新得更快,并使价格操纵无效。

4. 更严格的治理逻辑

在涉及到项目治理时,应该多方面考虑治理逻辑的严谨性,避免出现 Beanstalk Farms 那样的逻辑漏洞,一旦有个微小的漏洞,就有可能通过闪电贷无限放大,最后造成巨大的损失。

5. 必要时禁止跨合约调用

使用 require(tx.origin == msg.sender)​ 禁止跨合约调用。

3. 如何监控闪电贷攻击?

上述建议一定程度上可以减缓闪电贷攻击,但是有些方案过程复杂,且代价过大,在实际项目中难以实现。在无法完全解决闪电贷攻击的前提下,能及时准确的监控闪电贷攻击就显得尤为重要。比如使用监控平台或风险预警系统来对交易进行实时监控。


智能合约安全:闪电贷攻击
https://alphafitz.com/2022/10/24/security-flashload-attack/
作者
alphafitz
发布于
2022年10月24日
许可协议