预言机操纵与闪电贷 (Oracle Manipulation & Flash Loan Attacks)
这是 2020 年以来 DeFi 领域损失金额最大的一类攻击。它通常不是一个独立的代码缺陷,而是两种机制的叠加:
- 预言机:合约获取链外/外部数据的通道,喂价错误会直接污染所有依赖它的估值逻辑;
- 闪电贷:让攻击者在单笔交易内免费借用天量本金,把“操纵价格需要巨额资金“这个门槛抹平。
预言机是什么
合约运行在沙箱里(见 EVM 与内部状态),无法主动访问外部世界。“预言机“就是外部数据进入合约的通道。DeFi 中最常见的是价格预言机——抵押借贷、清算、流动性挖矿估值全都依赖它。常见实现按可信度从低到高:
| 喂价来源 | 原理 | 风险 |
|---|---|---|
| AMM 现货价 | 直接读 getReserves() 算比值 | 单笔交易即可改写,最危险 |
| TWAP | 时间加权平均价 | 窗口选择不当仍可被操纵 |
| 预言机网络 | Chainlink 等多节点聚合喂价 | 需校验数据新鲜度;长尾资产无覆盖 |
| 内部记账 | 协议自己维护的累积汇率 | 不受外部价格影响,但设计复杂 |
漏洞原理:现货价可以被单笔交易改写
以 Uniswap V2 的恒定乘积池为例,池内 reserveETH = 100、reserveUSDC = 100,000,现货价即储备比值:
100,000 / 100 = 1000 USDC/ETH
攻击者用 10,000 ETH 单笔卖出:新储备为 10100 : 990,现货价瞬间变成约 10 USDC/ETH——被压到 1/100。这笔交易结束后价格会因套利回归,但在同一笔交易内,任何读取这个现货价的合约看到的都是被扭曲的价格:
// 受害模式:直接用 AMM 现货价当抵押品估值
contract Lending {
IUniswapV2Pair public pair;
function borrow(uint256 collateralAmount) external {
// 用当前池价给抵押品估值 —— 漏洞所在
(uint112 r0, uint112 r1, ) = pair.getReserves();
uint256 price = r1 * 1e18 / r0; // 可被单笔 swap 改写
uint256 value = collateralAmount * price;
require(value >= minBorrow, "insufficient collateral");
// ... 按虚高估值放出真金白银
}
}
闪电贷:攻击的原子性原语
闪电贷允许用户在一笔交易内借出任意金额,只要交易结束前归还(连本带费),否则整个交易回滚。这利用了 EVM 交易原子性——它不是漏洞,而是协议功能:
- 接口已标准化为 EIP-3156,Aave、Uniswap(flash swap)等均支持;
- 费用极低(Aave V3 默认 0.05%,由治理参数
FLASHLOAN_PREMIUM_TOTAL控制且可调),金额不设上限; - 失败零成本:交易回滚只损失 gas。
对攻击者意味着:操纵一个百万美元级资金池所需的千万级本金,可以免费借来,用完即还。“需要巨额本金“从此不再是安全假设。
完整攻击流程(三段式)
- 借:闪电贷借入用于砸盘/拉盘的资产;
- 扭曲:在喂价来源的池子里单笔大额 swap,把现货价推到极端;
- 收割:在受害协议上按扭曲价格行动——超借抵押贷款、清算他人头寸、低买高卖份额——获利后恢复池价、归还闪电贷。
contract Attacker {
function attack() external {
// 1. 借入巨额 ETH
AAVE_POOL.flashLoanSimple(address(this), ETH, 10_000e18, "", 0);
// 2~3. 在 swap 回调中:砸盘 → 用虚低价格在受害协议套利 → 归还
}
function executeOperation(address, uint256 amount, uint256 premium, address, bytes calldata) external {
// 砸盘扭曲价格
ROUTER.swapExactTokensForTokens(amount, 0, path, address(this), block.timestamp);
// 在受害协议上按被扭曲的价格收割 ...
// 归还闪电贷本息
uint256 owed = amount + premium;
ETH.approve(address(AAVE_POOL), owed);
}
}
历史案例
| 时间 | 事件 | 损失 | 关键点 |
|---|---|---|---|
| 2020-02 | bZx | 约 100 万美元 | 首次公开揭示“闪电贷 + 现货价操纵“组合拳 |
| 2020-10 | Harvest Finance | 约 2400 万美元 | 闪电贷扭曲 Curve 池价,套利金库份额定价 |
| 2022-10 | Mango Markets | 约 1.17 亿美元 | 长尾资产深度差,现货拉价后按虚高抵押开仓借出 |
| 2023-02 | BonqDAO | 约 1.2 亿美元 | 预言机(Tellor)喂价本身被操纵,抵押品估值虚高 |
事件复盘资料:rekt.news。具体机制需结合攻击交易、合约版本和原始调查报告确认。
防御措施
- 抛弃现货价,用 TWAP:Uniswap V2 的
price0CumulativeLast/ V3 的observe(secondsAgo)提供时间加权价。注意权衡:窗口越长越抗操纵但越滞后;窗口太短时攻击者可以提前一个窗口开始推价,仍有操纵空间。 - 用预言机网络并校验新鲜度:读 Chainlink 必须用
latestRoundData()并检查updatedAt是否过期、answer > 0,而非已废弃的latestAnswer();关键资产多源交叉。 - 单交易内价格不可信:估值逻辑不应依赖“本交易刚开始时的外部报价“——用协议内部记账(如 Aave 自己累积的 aToken 汇率)或本交易内不变的量。
- 长尾资产不给杠杆:没有可靠喂价的资产,不要提供借贷/杠杆功能;深度差的池子即使 TWAP 也不安全。
- 审查“谁能影响价格“:审计时的核心问题是——喂价的每一次更新,攻击者需要花多少成本?如果答案是“一笔闪电贷“,就还没有完成防御。
检查清单
- 协议中所有估值都来自哪里?(
getReserves、slot0、latestAnswer全部标出) - 是否存在单笔交易内可改写的喂价?
- TWAP 窗口多长?窗口内操纵需要多少成本?
- 预言机数据是否校验了新鲜度与有效性?
- 长尾资产是否被赋予了杠杆/借贷功能?
进阶(只读重入喂价、TWAP 成本、内部汇率):预言机操纵进阶