過去一週(2026年10月19日–2026年10月25日),本報告追蹤了兩起安全事件,估計損失約為 557萬 USD。詳細分析聚焦於 Ethereum 上的 X100 Freezer 治理權遭奪取事件。
| 日期 | 事件 | 類型 | 估計損失* |
|---|---|---|---|
| 2026年10月21日 | NEM Token | 業務邏輯缺陷 | ~82.0萬 USD |
| 2026年10月24日 | X100 Freezer | 治理設計缺陷 | ~475萬 USD |
*損失金額為約略估計。X100 Freezer 的數字涵蓋下文分析的代表性交易,與 Phalcon 的初步估計一致;總額不包含另行通報的後續資產轉出。NEM 的日期為鏈上交易日期;公開告警發布於 10 月 22 日 [1]。
- X100 Freezer:此事件顯示,治理資格立即生效與缺乏執行時間鎖相結合所帶來的風險:攻擊者利用閃電貸資金購買並凍結代幣,立即取得參議院過半席次,並在同一筆交易中通過及執行惡意的財庫提案。
Web3 頂尖安全審計服務商
上線前驗證設計、程式碼與業務邏輯
本週焦點:X100 Freezer
2026年10月24日,Ethereum 上的鏈上質押與流動性管理系統 X100 Freezer 遭到攻擊,本次分析的交易造成約 475萬 USD 的損失。治理設計缺陷使新取得的凍結餘額能立即賦予參議院席次,而提案核准與執行之間沒有時間鎖。攻擊者取得三個席次中的兩席,將財庫交換交易導向攻擊者控制的資金池,並出售取回的部分代幣,以償還閃電貸並獲利 [2]。
背景
X100 使用者將 X 代幣凍結於分級質押部位,以賺取獎勵。位於 0x79f3...2a46 的 Freezer 合約也管理該專案在 Uniswap V3/V4 和 PancakeSwap V3 上的流動性。它透過 DexTreasury 繼承 Senate 治理合約;參議院透過提案與投票決定財庫操作。
公開的 freeze() 入口點會將使用者的代幣轉入合約,接著呼叫私有的 _freeze() 函式,建立質押部位並更新其帳務紀錄。_tierTotal[owner][tier] 記錄各帳戶在每個等級中的凍結數量。_freeze() 更新這些餘額,並透過 _onStakeChanged(owner) 通知治理系統:
function _freeze(address owner, uint8 tier, uint256 amount) private returns (uint256 id) {
_settle(owner);
Account storage account = _accounts[owner];
if (account.firstFreezeAt == 0) {
account.firstFreezeAt = block.timestamp;
}
id = _create(owner, tier, amount, block.timestamp, block.timestamp);
_tierTotal[owner][tier] += amount;
totalFrozen += amount;
totalFrozenIn[tier] += amount;
_onStakeChanged(owner);
emit Frozen(id, owner, tier, amount);
}
帳戶若要加入參議院候選人名單,須呼叫 applyForSenate()。這會呼叫 _register(),記錄報名、收取候選人費用,並呼叫同一個質押更新掛鉤:
function _register(address account) internal {
require(!_registered[account], AlreadyRegistered(account));
_registered[account] = true;
_collectCandidacyFee(account);
emit Registered(account);
_onStakeChanged(account);
}
該掛鉤會略過未報名的帳戶。對於已報名的帳戶,它會維護一份依凍結餘額排序的名單,透過 _link() 插入或移動帳戶。isSenator() 將該名單中前 senateSize 個帳戶認定為參議員。系統支援 3 至 21 個席次,每席各有一票。凍結代幣受到出金期限限制,該期限由 _unfreezeAt() 根據部位的 frozenAt 時間戳記與質押等級計算。
參議員透過 propose() 提交允許的財庫操作;該函式會記錄提案,並自動為提案人投下贊成票。其他參議員透過 vote() 投票,而 _tally() 會統計現有席次中的贊成票。提案會記錄建立時間,以及由 votingPeriod 決定的截止時間;後者預設為三天。
v3Swap() 等財庫函式是 DexTreasury._isProposable() 中允許的提案目標。其 onlySenate 修飾器要求呼叫必須來自合約本身。執行路徑會使用已核准的提案資料進行這項自我呼叫。之後,Freezer._afterExecute() 會檢查剩餘的 X 餘額是否足以涵蓋 totalFrozen,以維持凍結本金總額。
漏洞分析
Freezer 的治理設計存在缺陷:新取得的質押在賦予參議院權限前不必等待,而已核准的提案在執行前也沒有時間鎖。
首先,用於排名的質押量直接取自目前的凍結餘額。_stakeOf() 會呼叫 frozenOf();後者加總 _tierTotal,既不檢查質押存續時間,也不讀取歷史餘額:
function _stakeOf(address account) internal view override returns (uint256) {
return frozenOf(account);
}
function frozenOf(address owner) public view returns (uint256 total) {
total = 0;
for (uint8 tier = 0; tier < TIERS; ++tier) {
total += _tierTotal[owner][tier];
}
}
上述入金與報名路徑會在同一呼叫序列中更新資格。雖然 _freeze() 會記錄部位的時間戳記,排名與參議員資格檢查卻不會使用這些時間戳記來強制執行最低持有期限。因此,資金充足的新候選人可以在同一筆交易中取得席次,並行使提案與投票權。代幣的出金期限不會延後這些治理權利。
其次,三天的投票期只決定提案何時到期。提案一經建立,_state() 就會回傳 Open,而提案在執行、取消或到期前會維持開放狀態。過半數檢查與經過時間無關:
function _state(Proposal storage proposal) private view returns (ProposalState) {
if (proposal.executed) {
return ProposalState.Executed;
}
if (proposal.cancelled) {
return ProposalState.Cancelled;
}
if (block.timestamp > proposal.deadline) {
return ProposalState.Expired;
}
return ProposalState.Open;
}
function _isPassed(uint256 support) private view returns (bool) {
return support * 2 > senateSize;
}
只要贊成票超過 senateSize 的一半,execute() 就會接受開放中的提案。它在呼叫財庫操作前,既不檢查自 createdAt 起是否經過最低時間間隔,也不要求核准後等待:
function execute(uint256 id) external {
Proposal storage proposal = _requireOpen(id);
uint256 size = senateSize;
(uint256 support, address[] memory seats) = _tally(id);
require(_isPassed(support), NotPassed(id, support, size));
proposal.executed = true;
(bool ok, bytes memory result) = address(this).call(proposal.data);
if (!ok) {
assembly ("memory-safe") {
revert(add(result, 32), mload(result))
}
}
_afterExecute(id);
emit ProposalExecuted(id, _msgSender(), support, size, seats);
}
自我呼叫符合 onlySenate 的要求,因此財庫存取檢查不會增加執行延遲。最後的凍結本金檢查既不限制提案的執行時間,也不強制財庫交換交易達到最低回報。這些條件使新取得的過半席次得以在同一筆交易中授權並執行財庫操作。
攻擊分析
以下分析依據交易 0x38c608...4d81d0。
-
步驟 1:攻擊者利用閃電貸資金購買
X代幣,並將代幣分配給兩個受其控制的合約。借入的資金支付了代幣購買款,用以建立兩個質押部位。 -
步驟 2:每個受控制的合約都將各自持有的
X凍結於 Freezer,並呼叫applyForSenate()。交易追蹤顯示,兩個合約在報名後都收到Ranked事件。其凍結餘額使它們在該筆交易中取得兩個參議院席次。攻擊發生時,參議院共有三席,因此控制兩席便取得通過財庫提案所需的過半席次。

- 步驟 3:攻擊者建立一個 PancakeSwap V3
X/USDT資金池,並提供流動性。其中一個受控制的合約提出財庫交換提案,並自動投下贊成票;另一個則投下第二張贊成票。達到過半門檻後,Freezer 執行了提案 7。其v3Swap()呼叫指定以 400,000X作為輸入,並指定amountOutMinimum = 0。財庫僅收到約 35.68USDT。交易追蹤記錄了votesFor = 2和senateSize = 3,顯示被攻擊者掌控的過半席次授權了這項操作。

- 步驟 4:攻擊者從新建立的 PancakeSwap V3 資金池撤出流動性,取回財庫的
X。他們透過既有的 Uniswap V3X/USDT資金池出售部分代幣,並償還閃電貸。還款資金來自後續出售,而非立即提領凍結的質押部位。
結論
此事件源於治理設計缺陷:目前的凍結餘額會立即賦予權限,而已核准的提案不必經過時間鎖即可執行。攻擊者結合這兩項條件,在閃電貸須償還前取得並運用過半席次。財庫隨後透過攻擊者控制的 PancakeSwap V3 資金池,將 400,000 X 換成約 35.68 USDT。攻擊者從該資金池撤出流動性,取回財庫的代幣,再透過另一個既有的 Uniswap V3 X/USDT 資金池出售其中一部分,並用所得款項償還閃電貸及保留利潤。
治理機制應要求最低質押存續時間,或根據當前交易之前某個區塊取得的歷史餘額快照,判定席次資格與投票權。這可防止在同一筆交易中注資的部位立即授權財庫資產轉移。核准與執行之間設置時間鎖,則應能防止奪權與轉出資產的完整流程在同一筆交易中完成,並留出時間審查或取消惡意提案。這些控制措施分別針對治理路徑的不同階段,應在智能合約審計中一併評估。
參考資料
[1] BlockSec Phalcon:NEM Token 事件分析
[2] BlockSec Phalcon:X100 Freezer 治理事件分析
關於 BlockSec
BlockSec 是提供全方位區塊鏈安全與加密貨幣合規服務的供應商。我們打造的產品與服務,協助客戶在協議與平台的完整生命週期中執行程式碼審計(包括智能合約、區塊鏈與錢包)、即時攔截攻擊、分析事件、追蹤非法資金,以及履行 AML/CFT 義務。
BlockSec 曾在知名會議發表多篇區塊鏈安全論文、通報數起 DeFi 應用程式的零時差攻擊、阻擋多起駭客攻擊並挽救超過 2,000萬 USD 的資產,以及保護價值數十億的加密貨幣。
-
官方 Twitter 帳號:https://twitter.com/BlockSecTeam
-
🔗 BlockSec 審計服務 : 提交申請



