Back to Blog

~$1800万损失:jaredFromSubway、Aztec等项目受损 | BlockSec周报

Code Auditing
June 25, 2026
11 min read
Key Insights

在过去一周(2026/06/15 - 2026/06/21),我们观察到 3 起值得关注的安全事件,总损失约为 $18.3M。

日期 事件 类型 预估损失
2026/06/18 Aztec 公共输入绑定不当 ~$2.2M
2026/06/20 LABUBU Token 配置错误 ~$1.1M
2026/06/20 jaredFromSubway 授权管理不当 ~$15M
  • Aztec:入选原因是该协议在三天内遭到第二次攻击,此次攻击通过其逃生舱电路实现,凸显了 ZK 证明绑定问题的反复出现。
  • jaredFromSubway:入选原因是该 MEV 机器人的合约在未验证消耗情况或撤销剩余授权的情况下,向不可信代币合约授予了许可,使攻击者得以累积并提取约 $15M。

Web3 最佳安全审计机构

在上线前验证设计、代码和业务逻辑

本周重点:jaredFromSubway

与传统授权利用攻击不同——传统攻击中,攻击者滥用受信任 DeFi 合约中的漏洞,提取用户授权给这些合约的资产——本次攻击针对的是相反方向:MEV 机器人作为套利操作的一部分,主动将自身资产的授权授予不可信的第三方合约。攻击者构建了一个虚假的交易环境(本质上是一个蜜罐),虚假的交换池发出真实的 SwapSync 事件,而虚假代币从不消耗已授予的许可,在提取之前不断积累这些对外授权,据报告总损失约为 $15M。

2026 年 6 月 20 日,以太坊上的 MEV 机器人运营商 jaredFromSubway 损失约 $15M [1]。根据链上分析,根本原因在于机器人合约中的授权管理不当:授权被授予给从未消耗这些授权的不可信包装合约,攻击者积累这些未消耗的许可,最终在单笔交易中提取了机器人的真实余额。

背景

jaredFromSubway 是以太坊上知名的 MEV 机器人运营商,专门从事三明治攻击和链上套利。受害合约(0x1f2f...f387)是其操作钱包之一,持有大量 WETHUSDCUSDT 的营运资金余额。

此类 MEV 机器人必须动态与链上出现的任意新代币和池进行交互。它们监控内存池、模拟交易,并自动批准代币交互以捕获套利机会。这种操作模式依赖于代币行为符合预期的假设:当执行交换时,代币合约通过调用 transferFrom 消耗已授予的许可。

漏洞分析

根本原因是 MEV 机器人在与不可信合约交互时的授权管理不当。

机器人在 Uniswap 池和路由器上执行各种套利路径。在大多数交互中,机器人通过 transfer 直接将代币推送到池中,机器人本身是 msg.sender,无需授权。然而,与包装风格代币合约的交互遵循拉取模型:机器人调用 wrapper.wrapTo(),在该调用内部,包装合约调用 realToken.transferFrom(bot, wrapper, amount) 拉取机器人的真实代币。由于 transferFrom 期间的 msg.sender 是包装合约而非机器人,因此需要事先进行 approve

  1. <real_token>.approve(tokenContract, amount) — 向包装合约授予真实代币的许可
  2. tokenContract.wrapTo() → 跨池多跳 swap()tokenContract.unwrap() — 包装真实代币,通过池路由,再解包回真实代币

机器人假设 wrapTo() 会通过 transferFrom 消耗许可,就像一个行为良好的包装合约那样。然而,机器人从未验证操作后许可是否实际被消耗,也未撤销任何剩余许可。如果 wrapTo() 不调用 transferFrom,则完整的许可将在操作后保留,成为持续的攻击面——任何持有此类许可的合约都可以随后调用 transferFrom 来转移机器人的真实资产。

攻击分析

根据链上重建,攻击者构建了一个包含三个组件的虚假交易环境,以利用上述漏洞:

  1. 虚假包装代币:每个虚假代币使用真实代币的名称,但在符号前加上 f(例如,名称为 USD Coin,USDC 的符号为 fUSDC)。它实现了 wrapTo()unwrap() 来模仿合法的包装器,以及一个仅限攻击者访问的 withdraw() 函数,通过 transferFrom 提取未消耗的许可。

  2. 虚假交换池:攻击者通过自行部署的工厂部署了约 44 个 Uniswap V2 风格的池。这些池将虚假代币相互配对,形成令人信服的交换路由。当调用 swap() 时,这些池发出与合法交易无法区分的真实 SyncSwap 事件。

  3. 攻击者精心设计的利润:在 unwrap() 期间,虚假代币通过 transfer 向机器人发送少量真实代币。机器人获得了真实利润,但这是攻击者刻意制造的,而非来自市场套利。

攻击者通过外部合约中的逐区块 getStatus() 开关控制这些组件。getStatus() 在与激活交易(设置 _getStatus = block.number)同一区块中调用时返回 1,否则返回 0。当 getStatus() == 0 时,wrapTo() 正常调用 transferFrom,许可被消耗。当 getStatus() == 1 时,wrapTo() 跳过 transferFrom——许可未被消耗——而 unwrap() 仍向机器人返回攻击者制造的代币。攻击者可能在希望积累许可时,通过构建者贿赂将激活交易与机器人的交易放置在同一区块。

攻击分三个阶段进行:

第一阶段:部署攻击基础设施

  • 步骤 1:攻击者在区块 25354424 至 25354519 之间跨区块建立基础设施。包括部署虚假代币工厂合约(0x81f2...0091),通过自行部署的工厂创建约 44 个虚假 Uniswap V2 池,为池注入初始代币余额以确保 swap() 调用成功,并向收割合约(0xb84d...df52)发送 0.01 ETH 用于 gas 和构建者贿赂。

  • 步骤 2:攻击者通过 CREATE2 批量生产虚假包装代币,每个代币模仿一个真实代币(使用真实名称但在符号前加 f),并携带一个仅限攻击者访问的 withdraw() 函数。CREATE2 提供了确定性地址,收割合约可以循环遍历。

第二阶段:建立信任并积累授权

  • 步骤 3(初步建立信任):在最早的交易中(例如,区块 25354425 的 0x542d...362b),虚假代币没有 getStatus() 开关——wrapTo() 直接调用 transferFrom,消耗许可。机器人正常批准、包装、交换、解包并获利。这将虚假代币确立为有利可图的交易机会。

  • 步骤 4(持续建立信任):在随后的交易中(例如 0x085e...37e51),getStatus() 开关已部署但返回 0(与激活交易不在同一区块)。wrapTo() 仍然调用 transferFrom 并消耗许可。机器人继续获利并保持交互。

  • 步骤 5(积累):从区块 25360519 的 0x8560...1915 开始,攻击者通过构建者贿赂将激活交易与机器人的交易放置在同一区块,使 getStatus() 返回 1。在此模式下,wrapTo() 跳过 transferFrom——许可未被消耗——但 unwrap() 仍向机器人发送少量真实代币。机器人看到一笔有利可图的操作并保留了授权。在约 600 个区块(约 13 笔交易)内,机器人在 WETHUSDCUSDT 上重复此模式,在所有三种真实资产上积累了未消耗的许可。

第三阶段:收割

  • 步骤 6:攻击者在收割交易 0x2be870...cf3e65 中对所有虚假代币调用 withdraw(),利用未消耗的许可调用 transferFrom,将机器人的真实余额转移给攻击者。其中包含 0.01 ETH 的构建者贿赂以确保区块打包。收割仅从受害合约中提取了 1,474.58 WETH + 2,870,573 USDC + 2,035,760 USDT(约 $7.5M)。

已识别的攻击交易导致约 $7.5M 的损失,根据 jaredFromSubway 的声明 [1],总损失约为 $15M。

结论

本次事件的根本原因是 MEV 机器人在与不可信合约交互时的授权管理不当。与传统授权利用攻击(攻击者滥用受信任 DeFi 合约中的漏洞提取用户授权资产)不同,本次攻击方向相反:机器人作为套利操作的一部分,主动将自身资产授权给不可信的第三方合约。攻击者积累这些对外的未消耗授权,并在单笔交易中完成收割。

为降低未来类似风险,与不可信代币合约交互的机器人合约应在每次操作后验证授权是否被消耗,并撤销任何剩余许可。在看似成功的交易后出现未消耗许可,是恶意代币行为的强烈信号。

开始使用 Phalcon Explorer

深入分析交易,明智决策

立即免费试用

本周更多事件

Aztec

2026 年 6 月 18 日,Aztec 逃生舱 ZK 电路中缺少等式约束,使攻击者得以从以太坊上的旧版 RollupProcessor 合约中提取约 $2.2M(1,158 ETH、150K DAI 和约 0.47 renBTC[2][3]。这是三天内 Aztec 遭受的第二次攻击(第一次攻击已在我们的上期报告中介绍,目标为升级后的 RollupProcessorV3),通过相关的 ZK 电路公共输入绑定漏洞实现。

背景

Aztec 旧版 RollupProcessor 包含一个 escapeHatch 函数:这是一种安全机制,当 rollup 运营商停止处理时,允许任何人提交单笔交易证明。与 processRollup(需要授权提供者)不同,逃生舱根据区块号在周期性窗口中开放,任何人均可调用:

function escapeHatch(
    bytes calldata proofData,
    bytes calldata signatures,
    bytes calldata viewingKeys
) external override whenNotPaused {
    (bool isOpen, ) = getEscapeHatchStatus();
    require(isOpen, 'Rollup Processor: ESCAPE_BLOCK_RANGE_INCORRECT');
    processRollupProof(proofData, signatures, viewingKeys);
}

逃生舱使用专用的 ZK 电路(escape_hatch_circuit)来处理一笔合并-分割交易:从默克尔树中消耗输入票据并创建输出票据。电路必须验证输入票据存在于当前数据树中(使用 old_data_root 进行默克尔成员证明),然后将相同的根作为公共输入暴露,供 L1 合约与链上状态进行校验。

漏洞分析

该漏洞位于逃生舱电路(escape_hatch_circuit.cpp)中。old_data_root 值被转换为两个独立的见证值,但两者之间没有等式约束相连。

第一个见证值(第 33 行)被传入合并-分割电路组件,用于默克尔成员证明,以验证输入票据存在于数据树中:

join_split_inputs inputs = {
    // ...
    witness_ct(&composer, tx.js_tx.old_data_root),        // 第 33 行:第一个见证值
    // ...
};
auto outputs = join_split_circuit_component(composer, inputs);

第二个见证值(第 50 行)独立创建并作为公共输入暴露(第 88 行),Solidity 合约提取该值并与链上数据根进行校验:

auto old_data_root = field_ct(witness_ct(&composer, tx.js_tx.old_data_root));  // 第 50 行:第二个见证值
// ...
composer.set_public_input(old_data_root.witness_index);    // 第 88 行:作为公共输入暴露

在 ZK 电路中,每次 witness_ct 调用都会创建一个独立变量。在第 33 行和第 50 行之间没有显式的 assert_equal,证明者可以为这两个见证值分配不同的值。在 Solidity 端,validateMerkleRoots 仅使用第 50 行的公共输入执行 require(oldDataRoot == dataRoot) 检查,对第 33 行使用的值不可见。

相同的未绑定模式也存在于 input_owneroutput_owner:这些值在第 38–39 行被见证(传入 join_split_circuit_component 进行所有权验证),又在第 111–112 行再次作为独立公共见证值暴露。但我们尚未发现针对这一漏洞的实际利用路径。

攻击分析

逃生舱电路已从 aztec-connect 代码库中移除 [4],但已部署的验证器合约仍包含 EscapeHatchVk 验证密钥,因此使用有漏洞电路生成的证明仍可通过链上验证。攻击发生时,合约已沉默 142 天,逃生舱窗口处于开放状态。攻击者地址在攻击前仅 14 小时通过 Union Chain 创建 [2]。攻击包含三个核心步骤:

  • 步骤 1:攻击者构建了一棵包含任意价值自有票据的虚假默克尔树。这些票据在真实的链上数据树(存储所有有效票据的默克尔树)中并不存在。

  • 步骤 2:攻击者利用上述未绑定见证值生成了逃生舱证明。第 33 行的见证值(用于合并-分割组件中的默克尔成员证明)被设置为虚假默克尔根(成员证明通过,因为虚构的票据存在于虚假树中,所有权检查通过,因为攻击者持有签名密钥)。第 50 行的见证值(作为 Solidity 检查的公共输入暴露)被设置为真实的链上数据根(Solidity 的 require(oldDataRoot == dataRoot) 检查通过,因为该值与合约存储的根匹配)。

  • 步骤 3:在电路和 Solidity 检查均满足的情况下,证明成功验证。合约将逃生舱交易视为合法交易并释放了资金。

攻击者在三笔交易中重复此过程(0x9e1d6a...6b03ca0xab306c...59c2b50x5c196c...4705c3),分别针对不同资产,提取了 1,158 ETH、150K DAI 和约 0.47 renBTC,总计约 $2.2M。

结论

本次事件的根本原因是逃生舱电路中 old_data_root 的两个见证值之间缺少等式约束。一个见证值用于合并-分割组件内部的私有票据成员验证,另一个作为 Solidity 检查的公共输入暴露。由于缺乏将两者绑定的约束,攻击者得以针对虚假默克尔树证明对虚构票据的所有权,同时 L1 合约看到的是有效的链上根。值得注意的是,从源代码中移除有漏洞的电路并未使已部署的验证器合约失效——旧版 RollupProcessor 上的 escapeHatch 函数在其区块号窗口开放时仍可调用。

为降低未来类似风险,当同一逻辑值在 ZK 电路的多处出现时,所有实例必须被显式约束为相等——对同一值进行独立的 witness_ct 调用是一个绑定漏洞。电路审计应系统性地验证每个公共输入是否与其所代表的电路内部值绑定。

开始使用 Phalcon Security

检测每一个威胁,发出重要警报,阻断攻击。

立即免费试用

参考资料

关于 BlockSec

BlockSec 是一家全栈区块链安全与加密合规提供商。我们构建产品和服务,帮助客户在协议和平台的完整生命周期内进行代码审计(包括智能合约、区块链和钱包)、实时拦截攻击、分析事件、追踪非法资金,并履行 AML/CFT 合规义务。

BlockSec 已在顶级会议上发表多篇区块链安全论文,披露了多个 DeFi 应用的零日攻击,拦截了多起黑客攻击并成功救援超过 2000 万美元,保护了数十亿加密货币的安全。

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit