Beosin 揭露低版本 Solana Anchor 框架中的關鍵 IDL 指令漏洞

iconMetaEra
分享
AI summary icon精華摘要
Beosin 的安全團隊發現,低版本 Solana Anchor 框架中存在一個關鍵的 IDL 指令漏洞。該漏洞允許攻擊者透過利用 AccountInfo 声明,劫持程式擁有的 PDA 帳戶,並在無需使用者權限的情況下分兩步竊取資金。此事件凸顯了在智能合約開發中建立更強大合規框架的必要性。隨著 MiCA(歐盟加密資產市場法規)即將實施,此類漏洞更強調了實時安全審計與遵守監管標準的重要性。

Anchor 是 Solana 生態系統中最主流的開發框架。它透過聲明式帳戶驗證、自動序列化和內置安全檢查等特性,大幅降低了開發門檻。然而,框架在提供便利性的同時,也在幕後向每個程式注入了一些開發者可能並不了解的內部指令。這些「隱藏」指令在特定條件下可被攻擊者利用,造成嚴重的資金損失。

本文 Beosin 安全團隊將揭示一個關鍵漏洞模式:在低版本 Anchor 中,當開發者使用 AccountInfo 來聲明程式擁有(program-owned)的 PDA 帳戶時,攻擊者可以透過 Anchor 自動注入的 IDL 指令,僅需兩步操作就能奪取帳戶控制權並清空其中的所有 SOL,整個過程無需獲取任何特權。

一、IDL 指令及相關機制分析

1.1 IDL 指令

Anchor 預設會在每個程式中注入一組 IDL(Interface Definition Language,接口定義語言)管理指令,除非在建構時明確啟用 no-idl feature。這些指令包括:

IdlCreateAccount:建立一個鏈上 IDL 帳戶

IdlWrite:向 IDL 帳戶 / 緩衝帳戶寫入資料

IdlSetAuthority:變更 IDL 帳戶的 authority(控制者)

IdlCloseAccount:關閉 IDL 帳戶,並將其全部 lamports 轉給指定接收方

IdlResizeAccount:調整 IDL 賬戶大小

IdlCreateBuffer:建立 IDL 緩衝帳戶(IDL Buffer)

IdlSetBuffer:使用緩衝帳戶中的數據覆蓋正式 IDL 帳戶

這些指令的設計初衷是用於鏈上 IDL 管理,但它們對程式擁有的帳戶具有特殊的操作能力(讀取和寫入資料、變更控制者、關閉並轉移 lamports)——這正是漏洞的核心所在。攻擊者無需調用開發者編寫的任何業務指令,直接調用這些內建指令即可。

1.2 IDL 緩衝帳戶

IDL 緩衝賬戶(IDL Buffer)是 Anchor 為了分段上傳大型 IDL 數據而引入的臨時賬戶。由於完整的 IDL(經 JSON 壓縮後)可能超過單筆交易的大小限制,Anchor 允許先透過 IdlCreateBuffer 建立一個緩衝區,再用多筆 IdlWrite 分批寫入,最後透過 IdlSetBuffer 一次性提交。

關鍵點在於 IDL 帳戶 / 緩衝帳戶的資料結構:它以一個固定佈局的標頭開頭,標頭中包含一個 authority: Pubkey 字段(本文測試輸出中稱之為 controller)。IDL 指令透過這個字段判斷「誰有權操作該帳戶」。

但問題在於:低版本 Anchor 的 IdlCreateBuffer 處理邏輯會將傳入的、由本程序擁有的任意賬戶當作緩衝賬戶來初始化,並將 authority 直接設置為交易簽名者。也就是說,只要一個賬戶的 owner 是本程序(例如程序的 PDA 金庫),攻擊者就能把自己設為其 controller,隨後再用 IdlCloseAccount 名正言順地將賬戶內的 SOL 全部轉走。

1.3 漏洞觸發條件

觸發此漏洞需同時滿足以下條件:

  • 使用低版本 Anchor:危險的 IDL 指令未做充分的帳戶判別,允許把業務帳戶誤當作 IDL 帳戶操作。
  • 未啟用 no-idl:程式在建構時保留了預設注入的 IDL 指令入口,攻擊面對外暴露。
  • 使用 AccountInfo 声明程式擁有的 PDA:開發者使用裸的 AccountInfo 承載資金帳戶(如金庫 PDA),缺少 Anchor 類型化帳戶(如 Account)自帶的 discriminator / owner 判別,使該帳戶在 IDL 指令看來與 IDL 帳戶「無法區分」。
  • 帳戶為程式所有且持有 lamports:owner == 本程式 是 IDL 指令能操作它的前提;持有 SOL 才有被清空的價值。

滿足以上條件後,攻擊者只需兩筆普通交易:先 IdlCreateBuffer 奪取 controller,再 IdlCloseAccount 轉走全部 SOL,即可完成攻擊,全程無需目標程式授予任何權限。

1.4 攻擊鏈

下面結合 Anchor 內部實現,逐步拆解攻擊者如何僅用兩條內置指令,把一個普通的資金 vault 「偽裝」成 IDL 賬戶並將其清空。

攻擊流程概覽

步驟 1  IdlCreateBuffer(treasury, signer = attacker)    
└─ treasury.controller  ==>  attacker   (成為控制器) 
步驟 2  IdlCloseAccount(treasury, authority = attacker, dest = attacker)  
  └─ treasury.lamports    ==>  attacker   (抽乾金庫)

第一步:IdlCreateBuffer 奪取 authority

Anchor 的內部實現大致如下:

#[derive(Accounts)] pub struct IdlCreateBuffer {  
#[account(zero)]          // ← Key: 一個其 discriminator 全為 0 的帳戶  pub buffer: Account,  
pub authority: Signer,}    
pub fn idl_create_buffer(ctx: Context) -> Result 
{ 
 let idl = &mut ctx.accounts.buffer;  idl.authority = *ctx.accounts.authority.key; // 設定 attack 為權限者  Ok(())}

#[account(zero)] 的含義是:接受一個 discriminator 全為零、被程式擁有的帳戶,當作未初始化的 IDL 帳戶來初始化。而 vault 恰好同時滿足這兩個條件:

vault 的狀態

條件

由程式擁有

init_if_needed 後 owner 被設為本程式

discriminator 全零

使用 AccountInfo 類型,Anchor 不寫入 discriminator,資料全零

於是攻擊者將 vault 傳入 IdlCreateBuffer 後:

  • Anchor 在 vault 的前 8 個位元組寫入 IdlAccount 的 discriminator;
  • 將攻擊者公鑰寫入 authority 字段。

至此,vault 被「偽裝」成了一個以攻擊者為 authority 的 IdlAccount——而它內部的 SOL 分文未動。

第二步:IdlCloseAccount —— 清空資金

#[derive(Accounts)] pub struct IdlCloseAccount {  #[account(mut, has_one = authority)]  // ← 檢查 authority == signer  pub account: Account,  pub authority: Signer,  #[account(mut)]  pub destination: AccountInfo,  // ← 攻擊錢包}

此時的 vault 已經完全通過校驗:

  • discriminator 匹配 IdlAccount
  • authority 字段 = 攻擊者公鑰
  • has_one = authority 校驗通過

因此,金庫內所有 lamports(用戶存入的全部 SOL)被合法地轉入攻擊者帳戶,金庫歸零。

根本原因:

在 deposit.rs 中使用 AccountInfo 而非有類型的 Anchor Account,是本漏洞的致命根源:

(1) 具有類型的帳戶(例如 Account)在初始化時會寫入該結構體專屬的 8 字節 discriminator;

(2) 一旦寫入了自身的 discriminator,Anchor 便無法再將其當作 IdlAccount(discriminator 不匹配),第一步的 IdlCreateBuffer 就會失敗,攻擊鏈隨之斷裂。

二、案例分析

以下測試基於 Anchor 0.31.0 構建的跨鏈橋合約 PoC。測試模擬一個真實的橋金庫(Bridge Treasury),其 owner 為橋程式本身,內含用戶存入的 1.001281 SOL。攻擊者錢包最初持有 2 SOL,且不具備任何特權。

項目

數值 / 說明

橋程式 (Bridge Program)

CJutNlr8d3oxdb11ReOLaZd5jPsqUxgwt2mv5e7equtE

金庫帳戶(Treasury)

DxGkzaMhP5Wy824GhoehErdD6MudfuEPGPwaV2s77FsM

金庫 Owner

= 橋程式(program-owned PDA)

金庫初始餘額

1.001281 SOL(用戶存入資金)

初始 Controller

0x0000...0000(全零,未設定)

攻擊者錢包初始餘額

2.000000 SOL

所需特權

NONE(無需任何授權)

交易筆數

2 笔

金庫最終餘額

0.000000 SOL(已被清空)

攻擊者獲利

+1.001281 SOL

PoC 運行截圖(tests/poc-idl-hijack.ts):

https://wdcdn.qpic.cn/MTMxMDI3MDE1MTgxMDU0NzA_812435_a37-yrU_dY02i1_I_1785900257?w=1080&h=1287

可以看到,Step 1 的 IdlCreateBuffer 將攻擊者設為金庫的 controller(餘額不變);Step 2 的 IdlCloseAccount 將金庫 1.001281 SOL 全部轉入攻擊者錢包,金庫餘額歸零。

修復/防護建議:

(1) 升級 Anchor 版本:最新版本已修復該問題(IDL 指令會嚴格區分 IDL 帳戶與業務帳戶),避免使用低版本 Anchor 是最直接、最根本的防禦手段。

(2) 建置時啟用 no-idl:在生產環境程式中明確關閉 IDL 指令注入,從源頭消除此攻擊面。

(3) 使用類型化帳戶而非裸 AccountInfo:以 Account / SystemAccount 等帶 discriminator 與 owner 校驗的類型承載資金帳戶,使其無法被 IDL 指令誤認。

(4) 最小化程序擁有的可清空帳戶:對存放資金的 PDA 增加明確的 owner / seeds / discriminator 約束,並在關鍵指令中校驗帳戶判別符。

結語

該漏洞本質是「框架隱藏指令 + 帳戶類型判別缺失」的組合。開發者應認識到 Anchor 預設注入的 IDL 指令是真實存在的攻擊面,升級框架版本並對資金帳戶使用強類型約束,即可有效杜絕此類未授權資金清空風險。

Beosin 是一家領先的區塊鏈安全與監管合規科技公司,專注於項目上線前的智能合約安全審計、項目運行時的安全風險監控與阻斷、盜竊追回、虛擬資產反洗錢(AML)以及調查追蹤。Beosin 已為全球 20 多個國家和地區的監管與執法機構、200 多家虛擬資產服務商以及 4500 多家 Web3 項目提供「一站式」區塊鏈合規產品 + 安全服務。歡迎點擊公眾號留言框,與我們聯繫。

免責聲明:本頁面資訊可能來自第三方,不一定反映KuCoin的觀點或意見。本內容僅供一般參考之用,不構成任何形式的陳述或保證,也不應被解釋為財務或投資建議。 KuCoin 對任何錯誤或遺漏,或因使用該資訊而導致的任何結果不承擔任何責任。 虛擬資產投資可能存在風險。請您根據自身的財務狀況仔細評估產品的風險以及您的風險承受能力。如需了解更多信息,請參閱我們的使用條款風險披露