Anchor 是 Solana 生态中最主流的开发框架。它通过声明式账户验证、自动序列化和内置安全检查等特性,极大地降低了开发门槛。然而,框架在提供便利性的同时,也在幕后向每个程序注入了一些开发者可能并不了解的内部指令。这些“隐藏”指令在特定条件下可以被攻击者利用,造成严重的资金损失。
本文Beosin安全团队将揭示一个关键漏洞模式:在低版本 Anchor 中,当开发者使用 AccountInfo<'info> 来声明程序拥有(program-owned)的 PDA 账户时,攻击者可以通过 Anchor 自动注入的 IDL 指令,仅需两步操作就能夺取账户控制权并清空其中的所有 SOL,整个过程无需获取任何特权。
一、IDL 指令及相关机制分析
1.1 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)
#[derive(Accounts)]pub struct IdlCreateBuffer<'info> {#[account(zero)] // ← Key: an account with its discriminator all 0pub 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 authorityOk(())}
#[account(zero)] 的含义是:接受一个discriminator 全为零、被程序拥有 的账户,当作未初始化的 IDL 账户来初始化。而 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 == signerpub 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 便无法再把它当作 IdlAccount(discriminator 不匹配),第一步的 IdlCreateBuffer 就会失败,攻击链随之断裂。
二、案例分析
以下测试基于Anchor 0.31.0 构建的跨链桥合约 PoC。测试模拟一个真实的桥金库(Bridge Treasury),其 owner 为桥程序本身,内含用户存入的 1.001281 SOL。攻击者钱包最初持有 2 SOL,且不具备任何特权。

可以看到,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项目提供“一站式”区块链合规产品+安全服务。欢迎点击公众号留言框,与我们联系。
