Solana Anchor开发框架的 IDL 指令攻击分析

作者:Beosin 发布:2026-08-03 21:15 收录:2026-09-03 09:16 1 次阅读 约 2113 字
摘要:IDL指令攻击本质是“框架隐藏指令 + 账户类型判别缺失”的组合,开发者应认识到 Anchor 默认注入的IDL指令是真实存在的攻击面
推荐理由:本文涵盖「Beosin」、「Anchor」、「IDL指令」等多个主题,重点关注 Beosin。

Anchor 是 Solana 生态中最主流的开发框架。它通过声明式账户验证、自动序列化和内置安全检查等特性,极大地降低了开发门槛。然而,框架在提供便利性的同时,也在幕后向每个程序注入了一些开发者可能并不了解的内部指令。这些“隐藏”指令在特定条件下可以被攻击者利用,造成严重的资金损失。

本文Beosin安全团队将揭示一个关键漏洞模式:在低版本 Anchor 中,当开发者使用 AccountInfo<'info> 来声明程序拥有(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<'info> 承载资金账户(如金库 PDA),缺少 Anchor 类型化账户(如 Account<'info, T>)自带的 discriminator / owner 判别,使该账户在 IDL 指令看来与 IDL 账户“无法区分”。

- 账户为程序所有且持有 lamports:owner == 本程序 是 IDL 指令能操作它的前提;持有 SOL 才有被清空的价值。

满足以上条件后,攻击者只需两笔普通交易:先 IdlCreateBuffer 夺取 controller,再 IdlCloseAccount 转走全部 SOL,即可完成攻击,全程无需目标程序授予任何权限。

1.4 攻击链

下面结合 Anchor 内部实现,逐步拆解攻击者如何仅用两条内置指令,把一个普通的资金 vault “伪装”成 IDL 账户并将其清空。

攻击流程概览
Step 1  IdlCreateBuffer(treasury, signer = attacker)    └─ treasury.controller  ==>  attacker   (become controller) Step 2  IdlCloseAccount(treasury, authority = attacker, dest = attacker)    └─ treasury.lamports    ==>  attacker   (drain the treasury)
第一步:IdlCreateBuffer 夺取 authority

Anchor 内部实现大致如下:

#[derive(Accounts)]pub struct IdlCreateBuffer<'info> {  #[account(zero)]          // ← Key: an account with its discriminator all 0  pub buffer: Account<'info, IdlAccount>,  pub authority: Signer<'info>,}    pub fn idl_create_buffer(ctx: Context<..>) -> Result<()> {  let idl = &mut ctx.accounts.buffer;  idl.authority = *ctx.accounts.authority.key; // set attack as authority  Ok(())}

#[account(zero)] 的含义是:接受一个discriminator 全为零、被程序拥有 的账户,当作未初始化的 IDL 账户来初始化。而 vault 恰好同时满足这两个条件:


vault 的状态
条件

owned by program

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<'info> {  #[account(mut, has_one = authority)]  // ← check authority == signer  pub account: Account<'info, IdlAccount>,  pub authority: Signer<'info>,  #[account(mut)]  pub destination: AccountInfo<'info>,  // ← attack wallet}

此时的 vault 已经完全通过校验:


- discriminator 匹配 IdlAccount 

- authority 字段 = 攻击者公钥 

- has_one = authority 校验通过 


因此vault 内所有 lamports(用户存入的全部 SOL)被合法地转入攻击者账户,金库归零。


根本原因:


deposit.rs 中使用AccountInfo<'info> 而非有类型的 Anchor Account,是本漏洞的致命根源:


(1) 有类型的 Account(如 Account<'info, Vault>)在初始化时会写入该结构体专属的 8 字节 discriminator;


(2) 一旦写入了自身的 discriminator,Anchor 便无法再把它当作 IdlAccountdiscriminator 不匹配),第一步的 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):

可以看到,Step 1 的 IdlCreateBuffer 把攻击者设置为金库的 controller(余额不变);Step 2 的 IdlCloseAccount 将金库 1.001281 SOL 全部转入攻击者钱包,金库余额归零。


修复/防护建议:

(1) 升级 Anchor 版本:最新版本已修复该问题(IDL 指令会严格区分 IDL 账户与业务账户),避免使用低版本 Anchor是最直接、最根本的防御手段。


(2) 构建时启用 no-idl:对生产环境程序显式关闭 IDL 指令注入,从源头消除该攻击面。


(3)使用类型化账户而非裸 AccountInfo:Account<'info, T> / SystemAccount 等带 discriminator 与 owner 校验的类型承载资金账户,使其无法被 IDL 指令误认。


(4) 最小化程序拥有的可清空账户:对存放资金的 PDA 增加明确的 owner / seeds / discriminator 约束,并在关键指令中校验账户判别符。


结语


该漏洞本质是“框架隐藏指令 + 账户类型判别缺失”的组合。开发者应认识到 Anchor 默认注入的 IDL 指令是真实存在的攻击面,升级框架版本并对资金账户使用强类型约束,即可有效杜绝此类未授权资金清空风险。


Beosin是一家领先的区块链安全与监管合规科技公司,专注于项目上线前的智能合约安全审计、项目运行时的安全风险监控与阻断、被盗追回、虚拟资产反洗钱(AML)以及调查追踪。Beosin已为全球20多个国家和地区的监管与执法机构、200多家虚拟资产服务商以及4500多家Web3项目提供“一站式”区块链合规产品+安全服务。欢迎点击公众号留言框,与我们联系。
































转载声明:本文转载自原发布平台 (作者:Beosin), 原文标题《Solana Anchor开发框架的 IDL 指令攻击分析》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。