智能合约安全:闪电贷攻击
智能合约安全:闪电贷攻击
闪电贷攻击
本文是视频 详解闪电贷攻击手段@BuidlerDAO 的学习笔记。
1. 什么是闪电贷?闪电贷与区块链安全讲解
闪电贷就是在单笔交易中贷出借款人的金额,在交易结束时,借款人必须偿还不少于贷款金额的数目。如果借款人做不到,则交易就会自动回滚,就像贷款根本没有发生一样。主要包括三点特征:
- 闪电贷是一种无抵押贷款;
- 所有闪电贷都是通过区块链上的智能合约完成的;
- 贷款过程是即时的,一个区块内完成贷款和还款。
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 代币。
攻击过程:
- 闪电贷 242W BUSD 并在稳定币 Nerve 3Pool 中兑换为 241 万枚 3NVR-LP;
- 攻击者将 241 万枚 3NRV-LP 添加至 ElevenNeverSellVault 合约后获得 241 万枚 11Pool3 Nerve。紧急取出在 ElevenNeverSellVault 合约添加的 241 万枚 3NRV-LP;
- 重复过程 2;
- 将通过攻击的 3NRC-LP 兑换为 BUSD,偿还闪电贷并将获利的资金转给钱包。
闪电贷攻击案例二
2022 年 4 月 17 日,算法稳定币项目 Beanstalk Farms 遭到闪电贷加治理攻击,黑客获利 7600 万美元,协议损失 8200 万美元。
攻击过程大致如下:准备阶段:由于在 BEAN 合约中,所有治理行动都有 1 天的延迟,即提案后 1 天才能开始投票,所以攻击者实际上是在前一天提出两个治理提案,其中,第一个提案(提议18)提取合约中所有的钱。下一个提案(提议19)将价值 25 万美元的 $BEAN 发送到乌克兰的捐款地址。该攻击主要利用了投票合约中的投票是通过代币余额查询和校验的,然后调用 emergencyCommit
进行紧急提交以执行该提议。
攻击阶段:
- 攻击者从 Synapse 协议桥(最初来自 Tornado.cash)获取初始资金;
- 利用闪电贷获得价值超 10 亿资金:从 Aave 获得 3.5 亿 DAI,5 亿 USDC 和 1.5 亿 USDT;从 Uniswap v2 获得 3200 万 BEAN;从 SushiSwap 获得 1160 万 LUSD;
- 这些代币被用来为 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 万美元的损失。
其原因在于查询价格的预言机设计存在问题。
攻击步骤:
- 通过从 USDT-PCD 合约中闪贷大量 USDT 来准备价格操纵;
- 此时数量发生变化,价格也发生了变化,调用
shouchan
函数以极低价格购买 PCD 代币,并锁仓; - 解锁,并通过 PancakeSwap 卖出。
本案例中是直接通过闪电贷获得代币发起攻击。
闪电贷攻击案例四
2022 年 3 月 15 日,OMNI 协议遭受闪电贷加重入攻击,黑客共获取了 362 万枚 USDC,169 万枚 xDAI,16 枚 WBTC,24 枚 WETH,价值超过 600 万美元。
其原因在于:
- ERC667BridgeToken 中的
transfer
函数中的钩子callafterTransfer
实现了调用目标合约的功能(使得能够多次发起调用); -
borrow
函数中并未按照检查-生效-交互模式实现逻辑,并且没有防重入修饰器修饰该函数(可以实现回调);
攻击流程:
- 从 SushiSwap 中进行闪电贷,209 万 USDC,337 万 WXDAI;
- 随后,攻击者创建了多个合约同时存款借贷然后进行重入获利,可以查看其中一个地址
0xbE8fe2aE087aeCcB1E46EF206368421c9212637B
。
2. 如何减缓闪电贷攻击?
1. 要求关键交易跨越两个区块
如果一个资本密集型交易需要跨越至少两个区块,用户需要至少在两个区块时间段取出贷款,那么闪电贷攻击将会失效。但是要达到这一效果,两个区块之间用户价值必须锁定,以防止其偿还贷款。
2. 时间加权平均定价
在价格操纵案例中,建议使用时间加权平均价格(TWAP)来跨多个区块计算流动性池中的价格。因为整个攻击交易序列需要在同一个区块内处理,但如果不操纵整个区块链就无法操纵 TWAP,从而可以避免闪电贷导致的瞬时价格异常。
3. 更高频率的价格更新机制
同样在价格操作案例中,可以适当增加流动性池向预言机查询并更新价格的频率,随着更新次数的增加,池中代币的价格会更新得更快,并使价格操纵无效。
4. 更严格的治理逻辑
在涉及到项目治理时,应该多方面考虑治理逻辑的严谨性,避免出现 Beanstalk Farms 那样的逻辑漏洞,一旦有个微小的漏洞,就有可能通过闪电贷无限放大,最后造成巨大的损失。
5. 必要时禁止跨合约调用
使用 require(tx.origin == msg.sender)
禁止跨合约调用。
3. 如何监控闪电贷攻击?
上述建议一定程度上可以减缓闪电贷攻击,但是有些方案过程复杂,且代价过大,在实际项目中难以实现。在无法完全解决闪电贷攻击的前提下,能及时准确的监控闪电贷攻击就显得尤为重要。比如使用监控平台或风险预警系统来对交易进行实时监控。