2023年8月3日,Arbitrum 链上的一个 MEV 机器人遭到攻击,导致 80 万美元损失。此次攻击的根本原因是用户输入验证不足。
考虑到 MEV 机器人与其合约之间错综复杂的交互过程,且这些合约并未开源,这表明非开源并不能保证安全性,对于 DeFi 协议而言尤其如此。
背景
MEV 机器人
MEV 机器人(最大可提取价值机器人)旨在识别并执行区块链上的盈利机会。 它通过分析待处理交易(即内存池中的交易)或链上状态,利用套利挖掘利润。
与典型的抢先交易(front-running)和夹心攻击(sandwich attack)MEV 机器人不同,此次攻击的目标 MEV 机器人专注于执行三角套利和债务清算等策略。这些机器人本身有助于稳定 AMM(自动做市商)的价格,并协助借贷协议进行清算以确保平稳运行,构成了 DeFi 生态系统健康运行的重要组成部分。
闪电贷
闪电贷是 DeFi 生态系统中一种独特的创新——一种无需抵押的借贷形式。你可以在无需任何抵押品的情况下通过闪电贷借入高达十亿美元的资金,前提是该笔贷款必须在同一笔交易内偿还。如果贷款未在该交易内偿还,交易将会回滚,就像从未发生过一样。
这种机制通常用于套利或利用其他 DeFi 策略来挖掘临时的市场效率低下。
漏洞分析
简述
由于对用户输入参数的验证不足,攻击者得以引入了一个“虚假闪电贷提供商”(FakeFlashloanProvider)。金库合约利用该提供商发起了一笔闪电贷。随后,金库合约为了结算这笔闪电贷,向该“虚假闪电贷提供商”授予了代币授权,导致资产未经授权被转移出金库。
详解
被攻击的合约如下:
Vault(金库):受害者合约 0xd614927acfb9744441180c2525faf4cedb70207f 作为“金库”,提供储备金并促进来自 AAVE 和 Balancer 等其他协议的闪电贷。Arbitrage Bot(套利机器人):易受攻击的合约 0x8db0efee6a7622cd9f46a2cf1aedc8505341a1a7,作为“套利机器人”在“金库”合约中扮演借款人的角色。
“套利机器人”中的函数 0x0582f20f() 是发起套利的主要入口点。
它首先调用“金库”中的 borrow() 获取原始本金,然后通过 delegatecall 调用 calldata 中指定的外部合约来执行套利逻辑,且没有进行验证。
function 0x0582f20f(...) {
...
v67, /* uint256 */ v68 = address(0xd614927acfb9744441180c2525faf4cedb70207f).borrow(address(v39), address(v9[0]), v29).gas(msg.gas);
...
// 0x4da91757 = swap(address,address,address,uint256,uint256,uint256,address)
MEM[MEM[64] + 32] = uint224(address(MEM[0 + v4[v69]])) | 0x4da9175700000000000000000000000000000000000000000000000000000000;
v82 = address(v76 >> 96).delegatecall(MEM[(MEM[64]) len 228], MEM[(MEM[64]) len 0]).gas(msg.gas);
...
v189 = v170.refund(0x410085df, address(v9[0]), address(v39), v68, address(v9[0]), v29, v186, 4 + MEM[64] + (varg2.length << 5) - (4 + MEM[64]) + 192).gas(msg.gas);
...
}
随后,它调用“金库”中的 0x512b7351(),向攻击者的 FakeFlashloanProvider 合约发起一笔闪电贷。
函数 0x512b7351() 要求 msg.sender 必须在允许名单内,但该检查通过之前的 delegatecall 被成功绕过。这是一个非常关键的步骤。
function 0x512b7351(...) public nonPayable {
...
if (_borrow[msg.sender] >= 1) {
v0 = !_refund;
}
require(v0, Error('BBVault: FORBIDDEN'));
...
v38 = v23.length;
v39 = v23.data;
_refund = keccak256(v23);
...
<FakeFalshloanProvider>.flashloan(...);
...
}
在闪电贷回调期间,“金库”中的 executeOperation() 首先将借出的资产转移给 MEV 机器人 0x8db0ef,然后调用其 0x7fe3ba8b()。
function executeOperation(...) {
...
require(_refund == keccak256(v3.data), Error('BBVault: STATUS'));
Token.transfer(ArbitrageBot, amountBorrowed);
<ArbitrageBot>.call(0x7fe3ba8b...);
}
“套利机器人”盲目信任此外部调用,将收到的资产转回给 FakeFlashloanProvider。
然而,“金库”未能识别这一点,并且仍然在 executeOperation() 结束时向 FakeFlashloanProvider 授予了用于偿还闪电贷的代币授权。
攻击过程
攻击交易:0x864c8cfb8c54d3439613e6bd0d81a5ea2c5d0ad25c9af11afd190e5ea4dcfc1f
攻击者调用“套利机器人”的 0x0582f20f(),该函数进而对攻击者的合约执行了 delegatecall。


随后,hack_contract_2 调用了受害者合约的 0x512b7351()。0x512b7351() 要求 msg.sender 必须在允许名单内,但该检查通过之前的 delegatecall 被成功绕过。

接着,受害者合约调用攻击者的 FakeFlashloanProvider 合约,将所有闪电贷资产转移给受害者合约,并调用受害者的 executeOperation()。

“套利机器人”的 0x7fe3ba8b() 再次对攻击者合约执行了 delegatecall,这次将所有资产转回给攻击者。
至此,攻击者的闪电贷提供商所借出的资产已得到偿还。

受害者(“金库”)向 FakeFlashloanProvider 授予代币授权,意图可能是偿还闪电贷。

攻击者利用此授权获利,使用 transferFrom 清空了受害者的资产。
安全建议
非开源代码并不能保证安全
认为不开源和代码混淆可以确保安全的想法是错误的。此次 MEV 机器人事件表明,秘密并不能阻止利用攻击,反而可能让开发者产生虚假的安全感。
严格的输入验证
必须仔细验证所有的合约交互和 calldata,特别是在处理闪电贷和交换回调等标准接口时。在合约设计和实现过程中,确保数据完整性和安全性应当是重中之重。
阅读本系列的其他文章:
- 引言:2023 年“最震撼”的十大安全事件
- #1:通过利用 Flashbots Relay 中的漏洞猎取 MEV 机器人
- #2:Euler Finance 事件:2023 年最大规模的一次黑客攻击
- #3:KyberSwap 事件:高超的舍入误差利用与极其微妙的计算
- #4:Curve 事件:编译器错误导致纯净的源代码生成了有缺陷的字节码
- #5:Platypus Finance:凭借幸运躲过三次攻击
- #6:Hundred Finance 事件:引发基于分叉协议漏洞的一系列精度相关攻击浪潮
- #7:ParaSpace 事件:与时间赛跑,阻止行业最关键的一次攻击
- #8:SushiSwap 事件:一次笨拙的救援尝试导致了一连串的模仿攻击
- #10:ThirdWeb 事件:受信任模块之间的不兼容性导致安全漏洞



