一个没绑定的参数,让 Aztec 凭空提走 215 万美元

核心要点:

  • Ethereum 和 Solana 上共 4 起事件,总损失约 $5.98M
  • 攻击类型涵盖缺少输入验证(Aztec 和 Raydium)、整数溢出和治理攻击
  • 两起典型事件存在共同模式:关键参数或账户未绑定到预期值,攻击者借此操纵了下游计算;漏洞代码均已在链上存在数年——Raydium 约 1,254 天,Aztec 的停运后部署超过 2 年

过去一周(2026/06/08 - 2026/06/14),在 Ethereum 和 Solana 上共检测到 4 起值得关注的安全事件,总损失约 $5.98M。


一、Aztec 事件分析

背景

2026 年 6 月 14 日,Ethereum 上的隐私 rollup 协议 Aztec Connect 发生安全事件,损失约 $2.15M。根本原因是 ZK 证明所验证的 rollup 交易集与 L1 结算处理边界之间的不匹配,使得证明路径和结算逻辑可以处理不同的交易列表。攻击者利用这一缺口在 rollup 状态中计入了无资金支撑的存款余额,随后通过正常结算流程将其提取。

Aztec Connect 是 Ethereum 上的隐私 rollup 协议,支持 L2 上的隐私交易。由于用户资金来源于 L1,需先存入 rollup 处理合约,才能在 L2 的 Merkle 树中表示为 notes。

存款过程分两个阶段:

阶段一: 用户调用 depositPendingFunds(),通过 increasePendingDepositBalance() 增加 userPendingDeposits[assetId][owner],并将代币转入 RollupProcessor。这在 L1 上创建了一笔待处理的存款。

阶段二: 用户提交存款证明,该证明随后被纳入 rollup 批次并添加到 L2 状态。当 processRollup() 执行时,decodeProof() 从编码的 calldata 中读取 numTxs,连同解码后的证明数据一起返回,两者随后传入 processRollupProof()

processRollupProof() 内部,依次调用两个函数。首先,verifyProofAndUpdateState() 对所有解码交易验证 ZK 证明并更新 rollup 状态。然后,processDepositsAndWithdrawals() 处理 L1 结算,仅遍历前 _numTxs 个槽位,对每笔存款调用 decreasePendingDepositBalance() 消耗对应的待处理存款(若用户未在阶段一实际存入资金,该调用会 revert,从而将 rollup 记账与 L1 的真实转账绑定)。

这种两阶段设计要求 L1 结算逻辑处理的交易集与 ZK 证明所验证的完全一致。如果两条路径对待处理的交易产生分歧,存款就可能在 rollup 状态中被计入,却未在 L1 上消耗对应的待处理余额。

漏洞分析

在 rollup 处理合约(0x7d65...2728)中,numTxs 未被有效绑定到 ZK 证明所强制执行的交易集。证明路径和结算路径因此可以处理不同的交易列表。

在链下的 rollup_circuit 中,num_txs 作为 witness 加载。电路用它来控制哪些槽位被视为真实交易,但未将其暴露为 public input。超出 num_txs 的槽位仍然会被递归验证,但其公共输入会被置零,因此不会计入 rollup 状态。

电路代码中 num_txs 未被绑定到已证明的交易集

在 Solidity 侧,decodeProof() 从 calldata 的 metadata 中读取 numTxs,而该 metadata 并未被复制到 verifyProofAndUpdateState() 所验证的重构 proofData 中。结算循环的边界同样不受 ZK 证明覆盖。

decodeProof 中 numTxs metadata 未被纳入验证范围的 proofData

攻击者可以将 numTxs 设置为低于实际解码的交易数量,使结算循环跳过那些已被证明计入 rollup 状态的交易。第一个解码槽位放入一笔无效交易(在结算扫描范围内),而真正的存款放在后续槽位(已被电路证明,但超出结算扫描范围)。证明会在 rollup 状态中计入该存款,但结算逻辑会完全跳过它,包括 decreasePendingDepositBalance() 调用。这使得 L1 上的待处理存款余额未被消耗,而 rollup 状态已经反映了该存款。

攻击过程

以下分析基于交易 0x074ec9...9aeeb1,攻击分两个阶段展开。

第一阶段:创建无资金支撑的余额

  • Step 1:攻击者提交了多个 rollup 批次,每个批次包含两笔解码交易:槽位 1 为一笔无效(垃圾)交易,槽位 2 为一笔真实存款,同时将 numTxs 设为 1。L1 结算逻辑仅处理了槽位 1 的垃圾交易,完全跳过了槽位 2 的真实存款。
  • Step 2:ZK 证明验证并计入了所有解码交易,包括槽位 2 的存款。由于结算逻辑未触及该存款,decreasePendingDepositBalance() 未被调用,L1 上的待处理存款余额保持不变。攻击者对七种不同资产重复此操作,在 rollup 状态中积累了无资金支撑的余额。

第二阶段:提取资金

  • Step 3:七笔无资金支撑的余额建立后,攻击者对每种资产发起标准提款。由于这些余额存在于 rollup 状态中,结算逻辑认为提款合法,L1 合约释放了相应资金——总计约 $2.15M。

Aztec 攻击流程概览

小结

这一漏洞并非密码学缺陷,而是 rollup 架构中两条关键代码路径之间的状态一致性缺陷。根本原因:numTxs 未作为 public input 暴露,Solidity 侧的解码器则从未经验证的 calldata metadata 中读取该值。缺少这一绑定,证明路径和结算路径就可以处理不同的交易列表。

修复方案是将 numTxs 绑定到 ZK 证明所验证的完整交易集,使两条路径始终处理相同的集合。任何将证明验证与 L1 结算分离的 rollup 设计,都必须确保两条路径处理的是同一组、且边界可验证的交易。

值得关注的是,Aztec Connect rollup 早在 2024 年初已宣布停运,计划于 2024 年 3 月 31 日前终止交易处理和提款服务。然而 rollup 处理合约在 2024 年 4 月 10 日仍通过一个 PR 进行了升级,导致该漏洞代码在协议正式停运后依然在链上运行了超过 2 年。


二、Raydium 事件分析

背景

2026 年 6 月 10 日,Solana 上 Raydium 旧版 AMM v3 程序的四个流动性池发生安全事件,损失约 $1.34M。提款处理函数未校验调用者传入的账户是否与池中存储的规范对应账户匹配,攻击者因此可以传入自己控制的账户来操纵支付计算,在数秒内提取了四个池的全部储备。

Raydium 的 AMM 是 Solana 上的恒定乘积做市商。每个池持有两个代币保管库,并铸造 LP 代币代表储备的按比例份额。当流动性提供者提款时,处理函数按比例计算支付金额并从两个保管库中转出。

在 Solana 上,每种代币由一个 Mint 账户定义,存储总供应量、精度和铸造权限。每个持有者的余额存储在独立的 Token 账户中,一个 Mint 可以对应多个不同持有者的 Token 账户。这与 EVM 不同——EVM 中单个 ERC-20 合约同时管理代币定义和所有用户余额。

提款公式中的 lp_supply 读取自池的 LP Mint 账户——即追踪 LP 总供应量的那个账户。计算的正确性取决于该值来自真实的 LP Mint。但在 Solana 上,调用者按位置传入每条指令所需的所有账户,因此处理函数必须校验每个调用者传入的账户是否与池状态中存储的规范账户匹配。

漏洞分析

被利用的程序(27haf8...8vQv)并未开源,且攻击发生后其可执行数据(ProgramData)已被关闭。以下分析基于从程序最后一次升级 buffer 重构的字节码,并与链上交易行为交叉验证。

在提款处理函数中,调用者传入的 LP Mint 账户未被绑定到池中记录的 amm.lp_mint。处理函数校验了池状态、PDA authority、两个保管库和用户账户的绑定,唯独缺少槽位 5(LP Mint)的检查。

由于 LP Mint 账户未被绑定,攻击者可以传入伪造的 LP Mint 账户。将其总 supply 设为 1,销毁 1 个代币后,支付比率为 1 / 1 = 100% 的储备。

该漏洞代码自程序最后一次升级(2023 年 1 月 3 日)以来一直在链上运行,在被利用前已存在约 1,254 天。

攻击过程

以下分析基于交易 1csN6v...3s7s

Step 1: 攻击者创建了一个伪造的 LP Mint 账户,decimals = 0,总 supply = 0。

Phalcon trace 显示伪造 LP Mint 账户的创建

Step 2: 攻击者初始化了一个绑定到该伪造 LP Mint 的 Token 账户,然后以 Mint 权限持有者身份向其中铸造了 1 个代币,将该 Mint 的总 supply 固定为 1。

Phalcon trace 显示向攻击者 Token 账户铸造 1 个代币

Step 3: 攻击者调用提款函数,将伪造的 LP Mint 传入对应的账户槽位,将 Step 2 创建的 Token 账户(持有 1 个伪造 LP 代币)作为 LP 来源。由于 withdraw_amount = 1lp_supply = 1,处理函数计算出 total_coin * 1 / 1total_pc * 1 / 1,即 100% 的储备(RAY/USDC 池为 893,700 USDC 和 66,837 RAY)。

Phalcon trace 显示使用伪造 LP Mint 的提款计算

Step 4: 处理函数销毁了攻击者的 1 个代币,并将全部储备从两个保管库中转出,完全清空了 RAY/USDC 池。

Phalcon trace 显示保管库被掏空与代币销毁

攻击者在约 15 秒内对另外三个池重复了相同手法。

小结

根本原因是一项缺失的账户验证:提款处理函数将调用者传入的 Mint 账户的 supply 用作 LP 供应量除数,却未将其绑定到池中记录的 amm.lp_mint。在 Solana 上,每个由调用者传入的账户都必须绑定到池状态中存储的规范对应账户。

正确的实现应拒绝 key 与池中记录不匹配的 LP Mint,并使用池内部的 LP 计数器计算赎回比例,而非外部传入的 Mint supply。被利用的合约是一个较早的部署版本(最后升级于 2023 年 1 月),攻击发生当天即被关闭。据 Raydium 团队表示,相关损失将由协议国库全额补偿。


总结

本周两起典型事件揭示了一个共同的漏洞模式:关键参数或账户未绑定到预期的受信任来源,导致攻击者得以向处理函数传入任意值。

对于 rollup 开发者而言,凡是将证明验证与结算逻辑分离的架构设计,必须通过 public input 将控制边界的参数(如 numTxs)纳入电路约束范围,确保两条路径始终处理同一组交易。对于 Solana 程序开发者而言,调用者传入的每一个账户都应当与链上存储的规范账户进行显式绑定验证,不能依赖外部传入的账户数据参与关键计算。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值