返回部落格

每週 Web3 安全事件彙整|2026 年 3 月 9 日至 3 月 15 日

2026年3月18日
閱讀約 23 分鐘

過去一週(2026/03/09 - 2026/03/15),BlockSec 偵測並分析了八起攻擊事件,估計總損失約為 $1.66M。下表彙整這些事件,後續小節則提供各事件的詳細分析。

日期 事件 類型 估計損失
2026/03/09 EtherFreakers 事件 業務邏輯缺陷 ~$25K
2026/03/10 Alkemi 事件 業務邏輯缺陷 ~$89K
2026/03/10 MT 事件 業務邏輯缺陷 ~$242K
2026/03/11 AAVE 清算事件 設定錯誤 ~$1.01M
2026/03/11 Planet Finance 事件 業務邏輯缺陷 ~$10K
2026/03/12 AM 事件 業務邏輯缺陷 ~$131K
2026/03/12 DBXen 事件 業務邏輯缺陷 ~$149K
2026/03/15 Goose Finance 事件 業務邏輯缺陷 ~$8K

EtherFreakers 事件

簡要摘要

2026 年 3 月 9 日,以太坊上的 NFT 遊戲 EtherFreakers 因錯誤的重複計算遭到利用,造成約 $25K 的損失。遊戲中的每個 NFT 都持有可提領的 ETH 餘額,稱為「能量」。依照遊戲機制,玩家可使用 attack(),讓一個 NFT 捕獲另一個 NFT 並取得目標的能量。然而,合約在結清帳務前,就先支付目標的餘額並轉移 NFT。接著,轉移鉤子讀取支付前尚未更新的資料,將其中一部分重新計入全域分紅池,使池中數值膨脹,卻沒有新的 ETH 作為支撐。攻擊者反覆利用此捕獲機制推高全域指數,再從一批 NFT 提走虛增的餘額。

背景

EtherFreakers 是一款鏈上 NFT 遊戲,每個 NFT(稱為「Freaker」)都持有稱為 energy 的可提領 ETH 餘額。系統運作方式類似分紅池:特定操作發生時,部分 ETH 會按比例分配給所有 Freaker。每個 Freaker 可領取的 ETH,由全域累加器 freakerIndex 與各代幣的份額權重 fortune 共同追蹤。

具體而言,帳務計算公式為:energyOf = basic + (freakerIndex - index) * fortune。執行 _dissipateEnergyIntoPool(amount) 時,freakerIndex 會增加,將 amount 的 80% 分配給所有 Freaker,20% 分配給創作者。透過 charge() 直接存入的款項只會增加 basic,不會影響 freakerIndex。因此,freakerIndex 的增加理應始終有實際進入系統的以太幣作為支撐。若 freakerIndex 在沒有相應 ETH 流入的情況下增長,Freaker 就能兌領超過合約實際持有量的以太幣。

漏洞分析

根本原因是 EtherFreak 合約(0x3A27...c0f33)中的執行順序錯誤。捕獲成功時,attack() 函式依序執行以下步驟:

  1. 第 237 行:透過直接轉移 ETH,將 targetCharge(目標 NFT 的全部能量)支付給防守方。此時能量已被花費。
  2. 第 240 行:呼叫 _transfer(defender, capturer, targetId) 轉移 NFT。在內部,_transfer() 會觸發 ERC-721 鉤子 _beforeTokenTransfer(),後者以 energyOf(targetId) 的 0.1% 呼叫 _dissipateEnergyIntoPool()。這是第一次呼叫 _dissipateEnergyIntoPool();由於步驟 5 尚未發生,它讀取的是尚未更新的值。
  3. 第 241 行:明確呼叫 _dissipateEnergyIntoPool(sourceSpent)。這是第二次呼叫,屬於正常的遊戲邏輯。
  4. 第 244-251 行:更新 sourceId 與 targetId 的 energyBalances。

錯誤出在步驟 2:由於 energyBalances[targetId] 尚未更新,鉤子仍會讀取支付前的餘額,並將已花費能量的一部分計入分紅池。步驟 1 的直接 ETH 支付與步驟 2 的資金池計入,都使用同一份能量,使 freakerIndex 在沒有新 ETH 支撐的情況下膨脹。

每次呼叫 _dissipateEnergyIntoPool() 時,freakerIndex 都會增加:

攻擊分析

以下分析基於交易 0x89e24d...9abd2942。

  • 步驟 1:借入 1,700 WETH。

  • 步驟 2:在攻擊者控制的地址下鑄造兩個新的 Freaker,代幣 590 和代幣 591。

  • 步驟 3:反覆呼叫遊戲的 attack(590, 591) 函式,並讓執行進入捕獲成功的分支。

  • 步驟 4:每次成功後,將代幣 591 轉回輔助方(helper),以便重複使用同一組代幣。

  • 步驟 5:每次成功循環都會使 freakerIndex 膨脹,超過系統實際保有的以太幣所能支撐的數值。

  • 步驟 6:指數達到足夠高的水準後,提領一批先前控制的 Freaker。代幣 ID 496 至 520 各提領 0.278052246002402082 以太幣。

  • 步驟 7:將提走的以太幣封裝為 WETH,償還 1,700 WETH 閃電貸,並保留約 7.498 WETH 作為利潤。

結論

根本原因在於 attack() 的捕獲成功流程:EtherFreakers 在結清目標代幣的能量狀態前,就支付了 targetCharge。隨後 _transfer() 觸發 _beforeTokenTransfer(),讀取支付前尚未更新的 energyOf(targetId),並將其中一部分計入資金池。這使 freakerIndex 在沒有新以太幣支撐的情況下增加,導致同一份目標能量既被計為支付款項,又被計為資金池投入。這是業務邏輯造成的數值膨脹漏洞,而非重入漏洞。

為降低未來發生類似風險的可能性:

  • 當同一筆交易的帳務尚在結算時,避免在轉移鉤子中根據可變狀態重新計算經濟數值。

  • 若轉移鉤子會讀取狀態變數,應確保執行順序不影響結果(例如在鉤子執行前,而非執行後,完成狀態結算)。


Alkemi 事件

簡要摘要

2026 年 3 月 10 日,以太坊上的 Alkemi 協議遭到利用,造成約 $89K 的損失。根本原因是帳務錯誤與業務邏輯缺陷。有缺陷的清算邏輯允許任何人在同一筆交易中清算自己的部位並從中獲利。此外,一項帳務錯誤使清算時對攻擊者自身抵押品的扣減遭到覆寫,讓攻擊者無須承擔預期成本就能取得清算獎勵。

背景

Alkemi 是借貸協議。當借款人的部位抵押不足時,任何人都可以呼叫 liquidateBorrow(),償還部分債務並以折扣價取得抵押品。為防止過度清算,協議將每筆交易可償還的金額限制為以下三個值中的最小值:

  1. 借款人目前的借款餘額(currentBorrowBalance_TargetUnderwaterAsset)。
  2. 套用清算折扣後,借款人的抵押品所能支應的最高還款額(calculateDiscountedBorrowDenominatedCollateral())。
  3. 使帳戶回到清算邊界所需的還款額(calculateDiscountedRepayToEvenAmount()),僅在市場 isSupported 時檢查。

漏洞分析

根本原因是 Alkemi 協議(0x4822...a888)中的業務邏輯缺陷與帳務錯誤。只要借款人仍有未償債務,currentBorrowBalance_TargetUnderwaterAsset 必然大於 0;只要借款人仍有抵押品,calculateDiscountedBorrowDenominatedCollateral() 的傳回值也必然大於 0。因此,AlkemiEarnPublic 協議實際上依賴 calculateDiscountedRepayToEvenAmount() 判斷特定貸款是否可被清算。在此函式中,需要清算的債務金額理應根據名為 accountShortfall_TargetUser 的變數計算。

然而,實際實作卻使用全域變數 closeFactorMantissa 計算允許償還的債務金額上限,並傳回該值。因此,攻擊者得以在同一筆交易中借款,並立即清算自己的部位。

此外,在 liquidateBorrow() 函式中,當清算人與借款人是同一地址時,變數 supplyBalance_TargetCollateralAsset 和 supplyBalance_LiquidatorCollateralAsset 指向同一個儲存槽。函式接著根據同一份初始餘額,分別計算「扣減後餘額」與「獎勵後餘額」,再依序將兩者寫回同一個儲存槽。由於扣減後餘額先寫入、獎勵後餘額後寫入,扣減效果遭到覆寫而消失,最終只留下獎勵後的結果。這使攻擊者得以進一步擴大利潤。

攻擊分析

以下分析基於交易 0xa170...6d9d。

  • 步驟 1:攻擊者從 Balancer 閃電貸借入 51e18 WETH。

  • 步驟 2:攻擊者將 51e18 WETH 解除封裝為 ETH,並存入 Alkemi 協議。

  • 步驟 3:攻擊者從 Alkemi 借入 39.5e18 ETH。

  • 步驟 4:攻擊者使用 39.5395e18 ETH 清算自己的部位。

  • 步驟 5:攻擊者從 Alkemi 提領 93.5e18 ETH。

  • 步驟 6:攻擊者償還閃電貸,獲利 43.4e18 ETH。

結論

根本原因是有缺陷的清算邏輯允許攻擊者在同一筆交易中借款、清算自己的部位並獲利,而錯誤的帳務邏輯進一步擴大了攻擊者的收益。

為降低未來發生類似風險的可能性:

  • 更新借款人與清算人的餘額時,協議應直接操作儲存變數,而非將餘額複製到暫存記憶體變數,分別計算後再寫回。

MT 事件

簡要摘要

2026 年 3 月 10 日,BNB Chain 上的通縮型代幣 MT Token 遭到利用,造成約 $242K 的損失。根本原因是交易限制邏輯存在缺陷,加上特殊轉帳條件的處理方式不一致。在通縮階段,當資金池儲備超過固定門檻時,合約會限制買入操作。然而,合約將特定精確金額(例如 2e17 MT)的轉帳視為推薦關係綁定操作,使攻擊者得以繞過買入限制並取得初始代幣。此外,限制邏輯依賴不完整的路徑偵測(isBuy),未涵蓋 Pair 到 Router 等間接交換路徑;白名單檢查更會提前略過關鍵驗證。攻擊者在未觸發限制或手續費的情況下累積 MT 代幣,透過可控的流動性操作和交易操縱 pendingBurnAmount,並迫使資金池進入代幣價格被人為推高的異常狀態。

背景

MT Token 是 BNB Chain 上具有內建交易限制的通縮型代幣。在通縮階段,當資金池中的 MT 儲備超過 21,000e18 時,合約會阻止買入操作。一旦儲備低於此門檻,通縮階段便會結束,買入也會重新開放。MT Token 另設有推薦機制:恰好轉帳 2e17 MT 或 1e17 MT 會被視為綁定推薦人的操作,而非一般交易。

漏洞分析

根本原因是 MT 合約(0x037E...b449)的買入限制設計有缺陷。在正常情況下,攻擊者於受限階段應無法取得 MT 作為啟動資金。然而,合約將恰好 2e17 MT 的轉帳視為推薦關係綁定操作,而非買入,使攻擊者能繞過買入限制,購買 2e17 MT。

此外,交易限制依靠 isBuy 分支阻止買入,卻未涵蓋「Pair 到 Router」路徑。由於 Router 和 Pair 都是白名單地址,此類轉帳會在白名單檢查處提前結束,不會進入買入限制邏輯。攻擊者因此能將買入的代幣導向 Router,再透過移除流動性將代幣取回至自己的地址。

攻擊分析

以下分析基於交易 0xfb57...fca6。

  • 步驟 1:攻擊者透過閃電貸借入 ~358,681e18 WBNB。

  • 步驟 2:攻擊者購買 2e17 MT,藉此繞過買入限制。

  • 步驟 3:攻擊者向 Pair 提供 4e12 WBNB 和 2e17 MT 以增加流動性。基於上述相同原因,此次轉帳也繞過了收費邏輯。

  • 步驟 4:攻擊者從 Pair 買入 ~10,000,000e18 MT 代幣並送至 Router,藉此繞過買入限制與收費邏輯。

  • 步驟 5:攻擊者移除一半的流動性部位,過程中取出 Router 持有的全部 MT 代幣,接著將取回的 MT 賣出換成 WBNB。在此步驟中,pendingBurnAmount 被操縱至約 9,000,000e18。

  • 步驟 6:攻擊者再次買入 ~10,000,000e18 MT 代幣,將資金池的 MT 儲備降至 ~6,756,516e18,低於 pendingBurnAmount。

  • 步驟 7:攻擊者移除剩餘一半的流動性部位,提領已購買的 MT 代幣,再呼叫 distributeDailyRewards() 從資金池銷毀 MT。結果,MT 儲備降至 21,000e18。

  • 步驟 8:攻擊者將所有 MT 換回 ~1,198e18 WBNB,償還閃電貸並實現利潤。

結論

這次漏洞利用源於錯誤的交易限制:攻擊者只要購買恰好 BINDING_AMOUNT 的 MT 代幣,就能繞過買入禁令。取得 MT 後,攻擊者先增加流動性,再將買入的 MT 送至 Router,最後移除流動性取回代幣,進一步繞過收費邏輯與買入限制。接著,攻擊者透過賣出操作累積 pendingBurnAmount,並執行銷毀,使資金池儲備進入異常狀態,從而以被推高的價格賣出 MT 獲利。

為降低未來發生類似風險的可能性:

  • 嚴格區分轉帳語意與交易邏輯。

AAVE 清算事件

簡要摘要

2026 年 3 月 11 日,AAVE 在以太坊上發生涉及 $21M 的錯誤清算,造成約 $1.01M 的損失。根本原因是 wstETH 的預言機價格錯誤,使原本健康的部位變得抵押不足。結果,使用者部位遭到清算,導致財務損失。

背景

AAVE 使用預言機轉接器為 wstETH 等封裝資產定價。CAPO(價格上限預言機)轉接器將基礎 ETH/美元價格乘以轉換比率(getRatio(),即一個 wstETH 值多少 ETH),推算出 wstETH 的價格。為防止比率遭到操縱,CAPO 採用以快照為基礎的增長上限:

maxRatio = snapshotRatio + maxGrowthPerSecond x (currentTime - snapshotTimestamp)

並在定價時限制 getRatio() 的輸出(若 currentRatio > maxRatio,則使用 maxRatio)。此機制實際上限制了比率及其所產生價格的最大上升幅度。

漏洞分析

根本原因是 CAPO 預言機錨點設定(0xe1D9...61Ef)中的時間與比率不匹配:雖然設定了快照時間戳與快照比率,但快照比率被設為低於真實的 wstETH/ETH 比率。結果,轉接器計算出的 maxRatio 低於即時比率,並將 getRatio() 向下限制,系統性低估 wstETH/美元的預言機價格。抵押品估值下降,使以 wstETH 作為抵押品的部位健康係數降低,導致原本健康的帳戶被錯誤判定為不健康並遭到清算。

攻擊分析

以下分析基於交易 0x9064...8a9c。

  • 步驟 1:清算人透過閃電貸借入 ~6304e18 WETH,並清算借款人。

  • 步驟 2:清算人償還閃電貸,完成清算。

結論

這次清算由錯誤的預言機價格設定造成。該設定錯誤地使本應維持健康狀態的借款人變為不健康,進而觸發其部位清算。

為降低未來發生類似風險的可能性:

  • 確保每次更新前都驗證關鍵參數的正確性。

  • 在實作中加入驗證檢查,拒絕錯誤參數,防止錯誤設定成功生效。


Planet Finance 事件

簡要摘要

2026 年 3 月 11 日,BNB Chain 上的 Planet Finance 遭到利用,估計損失約 $10K。根本原因是協議錯誤地將借款人帳上借款餘額的增加視為應計利息,使攻擊者能反覆借款並觸發折扣結算,從而低估帳上債務。

背景

Planet Finance 是一個允許借款人以利息折扣還款的借貸協議。折扣採分級制度,取決於使用者質押的 GAMMA 與其在其他資產中的質押價值之比:比率越高,還款折扣越高。折扣分為三級,範圍從 0%(最低)到 50%(最高)。

漏洞分析

根本原因是協議(0x4c9E...F467)在 changeUserBorrowDiscount() 中結算借款人的折扣時,錯誤地將借款人帳上借款餘額的增加視為新產生的應計利息。結果,原本只應適用於應計利息的折扣,被錯誤套用至新借入的本金,不當減少了借款人的帳上債務。攻擊者可反覆執行 borrow 後接 changeUserBorrowDiscount 的循環,累積過多折扣,使鏈上記錄的負債持續低於實際借款額,最終利用差額獲利。

攻擊分析

以下分析基於交易 0x5f45...5ec9。

  • 步驟 1:攻擊者透過閃電貸借入 200,000e18 USDT。

  • 步驟 2:攻擊者使用 5,000e18 USDT 購買 WBNB,再以取得的 WBNB 買入 ~8,726,524e18 GAMMA。

  • 步驟 3:攻擊者先將取得的全部 GAMMA 質押至 gGAMMA 市場,再將剩餘的 USDT 存為抵押品,使還款折扣提高至 5%,並得以進行後續借款。

  • 步驟 4:攻擊者反覆呼叫 borrow,接著呼叫 updateUserDiscount,持續減少帳上債務。

  • 步驟 5:攻擊者最終償還債務、贖回抵押品並實現利潤。

結論

此事件源於 Planet Finance 在 changeUserBorrowDiscount() 中有缺陷的折扣結算邏輯。該邏輯錯誤地將借款人帳上借款餘額的增加視為新產生的應計利息,並對該差額套用利息折扣。攻擊者可反覆呼叫 borrow,再呼叫 updateUserDiscount,低估帳上債務,最終以低於實際負債的金額還款,從中獲利。

為降低未來發生類似風險的可能性:

  • 借貸協議應區分利息與新增借款。

AM 事件

簡要摘要

2026 年 3 月 12 日,BNB Chain 上的通縮型代幣 AM Token 遭到利用,估計損失約 $131K。AM Token 設有通縮機制,每次賣出都會額外從流動性池銷毀代幣,永久移除代幣以減少總供給量。然而,銷毀並非立即執行:合約會將賣出全額記錄為 toBurnAmount,並延至下一次賣出時才實際銷毀。這段記錄與執行之間的時間差,讓攻擊者得以買回 AM,將池中的 AM 儲備壓低至 toBurnAmount。當下一次賣出觸發延後執行的銷毀時,池中的 AM 儲備便會被完全清空,使價格升至極端水準,讓攻擊者賣出 AM 獲利。

背景

AM Token 是 BNB Chain 上的通縮型代幣。每次賣出時,合約會將交換涉及的 AM 數量記錄為 toBurnAmount,並在下一次賣出時,從流動性池銷毀該筆記錄的數量。實際上,賣出會觸發延後執行的銷毀,縮減池中的 AM 儲備。此外,在執行銷毀前,協議會將累積的 totalTokenFee 換成 USDT,再依照費用分配邏輯進行分配。

漏洞分析

根本原因是此代幣(0x27f9...213f)的賣出邏輯,會將交換涉及的全部 AM 數量累積為 toBurnAmount,並且直到下一次賣出時,才從 AM/USDT 交易對移除代幣,呼叫 pair.sync() 更新儲備並執行銷毀。此設計讓攻擊者能操縱池中的 AM 儲備、扭曲鏈上價格,並透過套利獲利。

攻擊分析

以下分析基於交易 0xd0d1...f859。

  • 步驟 1:攻擊者透過閃電貸借入 ~27,265,119e18 USDC 和 ~361,710e18 WBNB,再將兩者換成 ~100,423,811e18 USDT。

  • 步驟 2:攻擊者將 ~5,062e18 AM 代幣換成 USDT,使合約記錄的 toBurnAmount 變為 ~4,303e18。

  • 步驟 3:攻擊者以 USDT 換取 AM Token,將池中的 AM 儲備壓低至 ~4,303e18。

  • 步驟 4:攻擊者向資金池轉入 6 wei AM,觸發賣出路徑的銷毀邏輯。結果,合約銷毀了池中全部的 AM 餘額,使 AM 儲備降至 0。注意:協議會先嘗試在銷毀前將累積的手續費換成 USDT。這條費用轉換路徑也會觸發賣出分支的銷毀邏輯。銷毀執行、AM 儲備降至 0 後,費用交換失敗。由於該操作包在 try/catch 中,失敗不會使交易回復;執行反而會繼續,並將費用累加器重設為 0。

  • 步驟 5:攻擊者呼叫 pool.sync(),並將剩餘的 USDT 與 1 wei AM 轉入資金池。由於兩種代幣同時轉入,合約將此操作視為 addLiquidity,因此沒有累積 toBurnAmount。AM 儲備更新為 7。

  • 步驟 6:攻擊者將剩餘的 AM 代幣換成 USDT。在此交換過程中,將 AM 轉入 Pair 觸發了賣出路徑的銷毀邏輯,使 AM 儲備降至 1。此外,由於 totalFeeAmount 已在步驟 4 重設為 0,費用換成 USDT 的操作不再執行,攻擊者因此能以人為推高的價格賣出 AM。

  • 步驟 7:攻擊者償還閃電貸,實現剩餘利潤。

結論

此事件源於 AM Token 有缺陷的銷毀機制:每次賣出時,該機制會將交換涉及的 AM 累積為 toBurnAmount,並在下一次賣出時,從 AM/USDT Pair 銷毀該數量,同時呼叫 pool.sync()。這讓攻擊者能將 Pair 的 AM 儲備操縱至極低水準,再以人為推高的價格賣出 AM,從中提走 USDT。

為降低未來發生類似風險的可能性:

  • 限制每筆交易的最高銷毀量,並限制觸發銷毀的頻率,防止攻擊者在短時間內耗盡池中大部分代幣儲備。

DBXen 事件

簡要摘要

2026 年 3 月 12 日,以太坊及 BNB Chain 上的銷毀賺取收益協議 DBXen 遭到利用,總損失約 $149K。根本原因是 _msgSender() 與 msg.sender 的使用不一致。透過 forwarder 呼叫 burnBatch() 時,銷毀的 XEN 數量記錄在 _msgSender()(由攻擊者控制)名下,但週期紀錄卻更新在 msg.sender(即 forwarder)名下。這種分離使攻擊者能根據尚未更新的週期紀錄領取獎勵與費用,取得異常高額的款項。

背景

DBXen 是一個銷毀賺取收益的協議:使用者銷毀 XEN 代幣,以換取 DXN 獎勵及累積協議費用的一部分。其核心機制以週期運作。當使用者呼叫 burnBatch() 時,會發生兩件事:(1) 銷毀的 XEN 數量記錄在呼叫者的地址(由 _msgSender() 識別)名下;(2) XEN 合約透過 onTokenBurned() 回呼 DBXen,將呼叫者的週期紀錄(銷毀週期與 lastFeeUpdateCycle)更新至目前週期。

獎勵與費用透過 updateStats() 結算。獎勵依使用者在其銷毀週期內占 XEN 總銷毀量的比例計算。費用則根據自使用者上次記錄的週期以來累積的協議費用計算。兩項計算都仰賴使用者的週期紀錄保持更新。

漏洞分析

根本原因是 DBXen 協議(0xf5c8...2abd)的業務邏輯缺陷。_msgSender() 函式會檢查 msg.sender 是否為 forwarder。若是,便傳回 calldata 的最後 20 個位元組;在 forwarder 情境下,這個傳回值可被任意控制。然而,burnBatch() 會直接銷毀 msg.sender 持有的 XEN。因此,攻擊者可透過 forwarder 呼叫 burnBatch(),使協議銷毀 forwarder 持有的 XEN,並將 forwarder 的銷毀週期紀錄與費用更新週期紀錄都更新至目前週期。但與此同時,協議卻將銷毀的 XEN 數量記錄在 _msgSender() 對應的地址名下。

之後,攻擊者呼叫 claimFees(),進而觸發 updateStats()。由於 _msgSender() 地址的週期紀錄從未更新(銷毀週期與 lastFeeUpdateCycle 均維持在 0),updateStats() 會計算目前週期的獎勵,並計算自週期 0 以來累積的費用,涵蓋協議的全部費用歷史。攻擊者接著呼叫 claimFees() 和 claimRewards() 獲利。

攻擊分析

以下分析基於交易 0x914a5a...b808bc37。

  • 步驟 1:攻擊者先呼叫 Forwarder 合約的 registerDomainSeparator() 函式,使後續能夠呼叫 Forwarder.execute()。

  • 步驟 2:攻擊者在 Uniswap V2 資金池中,將 0.14e18 ETH 換成 13,900,000,000e18 XEN。

  • 步驟 3:攻擊者將 13,900,000,000e18 XEN 轉至 Forwarder 合約。

  • 步驟 4:攻擊者使用 Forwarder.execute(),授權 DBXen 使用 Forwarder 持有的 13,900,000,000e18 XEN。

  • 步驟 5:攻擊者使用 Forwarder.execute() 呼叫 DBXen.burnBatch(),銷毀 13,900,000,000e18 XEN。銷毀數量記錄在地址 0x425D3eC2DCeBE2c04bA1687504D43AFC6be7328d 名下;而在銷毀執行期間,XEN 透過 onTokenBurned() 回呼 DBXen,更新了 Forwarder 的相關週期紀錄。

  • 步驟 6:攻擊者使用 Forwarder.execute() 呼叫 DBXen.claimFees(),取得 65.36e18 ETH。

  • 步驟 7:攻擊者使用 Forwarder.execute() 呼叫 DBXen.claimRewards(),鑄造 2,305.4e18 DXN。

結論

此事件的根本原因是 DBXen 協議不一致地使用 _msgSender() 與 msg.sender。由於兩者的值可能不同,協議內部帳務因此出現不一致,讓攻擊者得以利用差異獲利。

為降低未來發生類似風險的可能性:

  • 在所有邏輯路徑中一致使用 _msgSender(),或確保依賴 msg.sender 的操作與依賴 _msgSender() 的帳務始終指向同一地址。

Goose Finance 事件

簡要摘要

2026 年 3 月 15 日,BNB Chain 上的收益耕作協議 Goose Finance 遭到利用,損失約 $8K。根本原因是 StrategyGooseEgg 中的份額定價順序有缺陷:deposit() 在將收穫的獎勵納入帳務前就鑄造份額,因此用於份額定價的總資產分母未包含這些獎勵,低於真實價值。這表示存款人取得的份額超過應得數量。呼叫 withdraw() 時,系統會收穫獎勵,使總資產增加,讓每份額價值提高。攻擊者在單筆交易中反覆存入與提領,多次鑄造數量過多的份額,再按修正後較高的價值贖回,將差額作為利潤取出。

背景

Goose Finance 是 BNB Chain 上的收益耕作協議。使用者資金從金庫流入策略,再由策略將資產質押至 MasterChef,賺取 EGG 獎勵。

與本事件相關的元件如下:

  • VaultChef(0x3f64...):追蹤使用者部位,並將資金轉交給 StrategyGooseEgg。

  • StrategyGooseEgg(0x0980...):透過 sharesTotal 與 wantLockedTotal 維護策略層級的帳務。

  • MasterChef(0xe70e...):接收質押資產並支付 EGG 獎勵。

  • WrappedEgg(0xb815...):以 1:1 比例將 EGG 封裝為供質押使用的 WEGG。

在操作流程上,存款會從 VaultChef 轉至 StrategyGooseEgg,再質押到 MasterChef。提領則由 VaultChef 發起,並由策略執行。

帳務上的一項關鍵預期是,份額定價應反映定價當下的策略總資產(已質押本金,加上策略已持有但尚未投入的獎勵)。然而,在 StrategyGooseEgg 中,deposit() 會在 _farm() 將尚未投入的資產計入 wantLockedTotal 前就鑄造份額,而 withdraw() 則可能觸發從 MasterChef 收穫獎勵。此順序構成下述漏洞的基礎。

漏洞分析

根本原因是 StrategyGooseEgg(0x0980...b26b)中,獎勵收穫與份額定價之間的帳務不同步。

在 StrategyGooseEgg 中,份額定價以 wantLockedTotal 為分母:shares = deposit * sharesTotal / wantLockedTotal。為確保公平,wantLockedTotal 必須反映策略實際持有的全部資產,包括留在合約中、尚未投入的 EGG 獎勵。然而,deposit() 在 _farm() 將尚未投入的獎勵計入 wantLockedTotal 前,就已鑄造份額。這使分母排除了尚未入帳的獎勵,低於真實總資產,導致存款人取得超過應得數量的份額。

此外,withdraw() 會呼叫 MasterChef.withdraw(),將已質押本金及待領取的 EGG 獎勵一併返還給策略。策略的帳務僅從 wantLockedTotal 扣除所請求的 _wantAmt,因此收穫的獎勵仍留在策略餘額中,卻未反映於 wantLockedTotal。這擴大了實際持有資產與帳上 wantLockedTotal 的差距,使後續 deposit() 的份額定價更加不準確。

攻擊分析

以下分析基於交易 0x86efdf...ce316223。

  • 步驟 1:攻擊者從兩個 Pancake 交易對閃電借入 EGG。

  • 步驟 2:首次存入 VaultChef/StrategyGooseEgg(10,170,000e18 EGG)。

  • 步驟 3:首次提領(12,593,884e18 EGG)從 MasterChef 收穫獎勵;359,561e18 EGG 轉入 StrategyGooseEgg,並作為尚未投入、尚未入帳的價值留存(R > 0)。

  • 步驟 4:第二次存入時,重複使用提領出的資金(12,593,884e18 EGG)。份額在尚未投入的價值結算前就已定價,因此此步驟鑄造了過多份額。

  • 步驟 5:第二次提領(12,826,027e18 EGG)實現過量鑄造份額所帶來的利潤(即比步驟 4 的存入額多出 232,143 EGG)。

  • 步驟 6:攻擊者償還閃電交換借款,並保留淨差額。

結論

此漏洞利用源於 StrategyGooseEgg 的份額定價順序缺陷:deposit() 在 _farm() 更新 wantLockedTotal 前就鑄造份額,而 withdraw() 可從 MasterChef 收穫暫時留存且未入帳的獎勵。這讓存款能依據尚未更新的分母鑄造份額,再按已更新的資產價值提領。

為降低未來發生類似風險的可能性:

  • 在計算份額鑄造與銷毀數量前,先結算獎勵並更新帳務。

  • 在實際計算時,以單一 totalAssets(staked + idle)為份額定價依據。

  • 加入不變條件測試,驗證在尚未投入的獎勵不為零時,shares_minted <= D * S / (A + R)。


關於 BlockSec

BlockSec 是提供全方位服務的區塊鏈安全與加密資產合規服務商。我們打造產品與服務,協助客戶在協議與平台的完整生命週期中,進行程式碼稽核(包括智慧合約、區塊鏈及錢包)、即時攔截攻擊、分析事件、追蹤非法資金,以及履行反洗錢與打擊資恐(AML/CFT)義務。

BlockSec 已在知名會議發表多篇區塊鏈安全論文、通報多起 DeFi 應用程式的零時差攻擊、阻止多起駭客攻擊並挽救超過 2,000 萬美元的資產,同時保障了價值數十億美元的加密貨幣安全。