刚拿到投票权 就能动用国库?X100 Freezer治理攻击复盘

刚拿到投票权 就能动用国库?X100 Freezer治理攻击复盘

X100 Freezer lost ~$4.75M on Ethereum on Oct 24, 2026, due to governance flaws. Attackers used flash loans to buy and freeze tokens, instantly gaining 2 of 3 Senate seats, then passed and executed a proposal to swap 400,000 X for only ~35.68 USDT.

核心要点

本期涵盖 BNB Chain 和 Ethereum 上的两起事件,按下述统计范围,估计损失合计约 $5.57M。

NEM Token 的税费结算存在业务逻辑缺陷;X100 Freezer 的治理设计缺陷则使攻击者能够利用闪电贷资金夺取参议院控制权。

X100 Freezer 根据当前冻结余额授予治理权限,并允许提案立即执行,新取得的投票权因此可以直接支配国库操作。

预计阅读时长 8 分钟

过去一周(2026/10/19 至 2026/10/25),本期周报跟踪了两起安全事件,估计损失合计约 $5.57M。本期重点分析 Ethereum 上的 X100 Freezer 治理攻击。

*损失均为估算值。X100 Freezer 金额对应下文分析的代表交易,与 Phalcon 最初的估算一致;合计金额未计入另有报道的后续资金提取。NEM 日期采用链上交易日期,公开告警发表于 10 月 22 日[1]。

X100 Freezer:该事件展示了治理资格即时生效与缺少执行时间锁叠加的风险。攻击者用闪电贷资金购入并冻结代币,立即取得多数席位,在同一笔交易内通过并执行恶意国库提案。

本周看点: X100 Freezer

2026 年 10 月 24 日,Ethereum 上的 X100 Freezer 遭到攻击。Freezer 是一个链上质押与流动性管理系统,本期分析的交易造成约 $4.75M 损失。

其治理设计存在缺陷:新形成的冻结余额能够立即换取参议院席位,提案批准与执行之间也没有时间锁。攻击者取得三个席位中的两个,指使国库向其控制的池子兑换代币,再出售部分取回的代币,偿还闪电贷并获利[2]。

背景

X100 用户将 X 代币冻结在不同档位的质押仓位中以获取奖励。Freezer 合约0x79f3...2a46同时管理项目在 Uniswap V3/V4 和 PancakeSwap V3 上的流动性。

它通过 DexTreasury 继承 Senate 治理合约,由参议院通过提案和投票决定国库操作。

用户调用公开入口 freeze() 后,合约先转入用户的代币,再调用私有函数 _freeze() 创建质押仓位并更新记账。_tierTotal[owner][tier] 记录各账户在各档位的冻结数量。

_freeze() 更新这些余额,并通过 _onStakeChanged(owner) 通知治理系统:

账户通过 applyForSenate() 申请加入参议院候选名单。该入口调用 _register(),记录注册状态、收取候选人费用,并调用同一个质押量更新钩子:

该钩子会跳过未注册账户。对于已注册账户,它通过 _link() 插入或移动账户,维护按冻结余额排序的列表。isSenator() 将列表中的前 senateSize 个账户识别为参议员。系统支持 3 至 21 个席位,每席一票。

冻结代币还受取款期限约束,_unfreezeAt() 根据仓位的冻结时间戳 frozenAt 和质押档位计算解冻时间。

参议员通过 propose() 提交允许执行的国库操作,合约记录提案,并自动为提案人投出一张赞成票。其他参议员通过 vote() 投票,_tally() 统计当前席位中的赞成票。

提案记录创建时间,并根据默认值为三天的 votingPeriod 设置截止时间。

DexTreasury._isProposable() 允许提案调用 v3Swap() 等国库函数。这些函数的 onlySenate 修饰器要求调用来自合约自身,执行流程则使用获批提案的数据发起自调用。

操作完成后,Freezer._afterExecute() 检查剩余 X 余额是否覆盖 totalFrozen,以保留冻结本金总额。

漏洞分析

Freezer 的治理设计存在两处缺陷:新取得的质押量在转化为参议院权限之前缺少等待期,获批提案在执行之前也缺少时间锁。

首先,席位排名直接使用当前冻结余额。_stakeOf() 调用 frozenOf(),对 _tierTotal 求和,没有检查质押时长,也没有读取历史余额:

背景中的存入和注册流程会在同一调用序列内更新资格。虽然 _freeze() 为仓位记录了时间戳,但排名与参议员身份检查没有利用这些时间戳设置最短持有期。因此,质押量足够的新候选人可以在同一笔交易内取得席位并行使提案和投票权。

代币取款期限不会延迟这些治理权限生效。

其次,三天投票期只决定提案何时过期。提案创建后,_state() 便返回 Open,直到提案被执行、取消或过期。多数票检查也与经过的时间无关:

只要提案处于开放状态、赞成票超过 senateSize 的一半,execute() 就允许执行。调用国库操作之前,它既没有检查距离 createdAt 是否已过最短期限,也没有检查批准后是否经过等待期:

自调用满足 onlySenate,因此国库访问检查不会增加执行延迟。最后的冻结本金检查既不约束提案执行时间,也不限制国库兑换的最低回报。这些条件使新形成的多数席位能够在一笔交易内批准并执行国库操作。

攻击分析

以下分析基于交易0x38c608...4d81d0。

Step 1: 攻击者用闪电贷资金购买 X 代币,再将代币分配给两个受控合约。这笔借入资金用于购入随后建立两个质押仓位所需的代币。

Step 2: 两个受控合约分别在 Freezer 中冻结 X,并调用 applyForSenate()。交易轨迹显示,两者注册后均触发了 Ranked 事件。凭借各自的冻结余额,它们在同一笔交易内取得两个参议院席位。

攻击发生时共有三个席位,因此控制两个席位即可获得通过国库提案所需的多数票。

Step 3: 攻击者创建 PancakeSwap V3 的 X/USDT 池并提供流动性。其中一个受控合约发起国库兑换提案,自动投出一张赞成票;另一个合约投出第二张赞成票。达到多数票门槛后,Freezer 执行了提案 7。

其 v3Swap() 调用以 400,000 枚 X 作为输入,并设置 amountOutMinimum = 0,国库最终仅获得约 35.68 枚 USDT。轨迹中的 votesFor = 2 和 senateSize = 3 表明,该操作由攻击者掌握的多数票授权。

Step 4: 攻击者从其新建的 PancakeSwap V3 池中撤回流动性,取回国库卖出的 X,再通过已有的 Uniswap V3 X/USDT 池出售其中一部分,偿还闪电贷。还款资金来自这次后续出售,并非立即取出被冻结的质押代币。

结论

本次事件源于治理设计缺陷:当前冻结余额可以立即赋予治理权限,获批提案也无需经过时间锁便可执行。

攻击者利用这两个条件,在闪电贷必须归还之前取得并行使多数票权限,使国库通过攻击者控制的 PancakeSwap V3 池将 400,000 枚 X 兑换为约 35.68 枚 USDT。

攻击者随后从该池撤回流动性,取回国库卖出的代币,再到另一个原有的 Uniswap V3 X/USDT 池出售其中一部分,用所得资金偿还闪电贷并获利。

治理应设置最短质押时间,或根据早于当前交易所在区块的历史余额快照确定席位资格和表决权,避免在本交易内注资的仓位立即批准国库资金转移。提案批准与执行之间还应设置时间锁,阻止夺权和资金提取在一笔交易内全部完成,并为审查或取消恶意提案留出时间。

这些控制措施作用于治理流程的不同阶段,智能合约审计应结合整个流程评估其有效性。

参考资料

[1] BlockSec Phalcon: NEM Token incident analysis

[2] BlockSec Phalcon: X100 Freezer governance incident analysis

来源:金色财经