Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

预言机操纵与闪电贷 (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。

对攻击者意味着:操纵一个百万美元级资金池所需的千万级本金,可以免费借来,用完即还。“需要巨额本金“从此不再是安全假设。

完整攻击流程(三段式)

  1. 借:闪电贷借入用于砸盘/拉盘的资产;
  2. 扭曲:在喂价来源的池子里单笔大额 swap,把现货价推到极端;
  3. 收割:在受害协议上按扭曲价格行动——超借抵押贷款、清算他人头寸、低买高卖份额——获利后恢复池价、归还闪电贷。
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-02bZx约 100 万美元首次公开揭示“闪电贷 + 现货价操纵“组合拳
2020-10Harvest Finance约 2400 万美元闪电贷扭曲 Curve 池价,套利金库份额定价
2022-10Mango Markets约 1.17 亿美元长尾资产深度差,现货拉价后按虚高抵押开仓借出
2023-02BonqDAO约 1.2 亿美元预言机(Tellor)喂价本身被操纵,抵押品估值虚高

事件复盘资料:rekt.news。具体机制需结合攻击交易、合约版本和原始调查报告确认。

防御措施

  1. 抛弃现货价,用 TWAP:Uniswap V2 的 price0CumulativeLast / V3 的 observe(secondsAgo) 提供时间加权价。注意权衡:窗口越长越抗操纵但越滞后;窗口太短时攻击者可以提前一个窗口开始推价,仍有操纵空间。
  2. 用预言机网络并校验新鲜度:读 Chainlink 必须用 latestRoundData() 并检查 updatedAt 是否过期、answer > 0,而非已废弃的 latestAnswer();关键资产多源交叉。
  3. 单交易内价格不可信:估值逻辑不应依赖“本交易刚开始时的外部报价“——用协议内部记账(如 Aave 自己累积的 aToken 汇率)或本交易内不变的量。
  4. 长尾资产不给杠杆:没有可靠喂价的资产,不要提供借贷/杠杆功能;深度差的池子即使 TWAP 也不安全。
  5. 审查“谁能影响价格“:审计时的核心问题是——喂价的每一次更新,攻击者需要花多少成本?如果答案是“一笔闪电贷“,就还没有完成防御。

检查清单

  • 协议中所有估值都来自哪里?(getReserves、slot0、latestAnswer 全部标出)
  • 是否存在单笔交易内可改写的喂价?
  • TWAP 窗口多长?窗口内操纵需要多少成本?
  • 预言机数据是否校验了新鲜度与有效性?
  • 长尾资产是否被赋予了杠杆/借贷功能?

进阶(只读重入喂价、TWAP 成本、内部汇率):预言机操纵进阶