Solana程序安全:实际被利用的漏洞及其原因

mixbytes 发布于 2026-06-03 阅读 237

本文深入分析了Solana生态系统中常见的智能合约漏洞类型及其根源,包括账户图完整性缺陷、CPI信任边界问题、PDA命名空间和初始化竞争、Token语义错误等。

引言

Solana 已经悄然成为最活跃的链上程序生态系统之一。过去几年中,它从一个高吞吐量的新秀,成长为承载主要 DeFi 协议、消费者支付应用、交易基础设施和机构级应用的平台。该网络如今位列生产中吞吐量最高的区块链之一——这是其与 EVM 链根本不同的架构所带来的结果。

但规模也意味着风险。如今,数十亿美元的流动性在 Solana 生态系统中流动,保护用户和协议资金成为关键问题——这需要一种超越仅靠 EVM 合约审计所能提供的安全视角。Solana 独特的执行模型产生了一类在 Ethereum 上无法简单类比的安全漏洞,仅凭一般的 DeFi 审计经验并不足以发现它们。

本文剖析了 Solana 上实际导致事故的原因:真实的漏洞利用、最常见错误的架构根源,以及审计师在生产程序中遇到最多的漏洞类别实用图谱。

Solana 上“黑客攻击”的真正原因是什么?

与 Ethereum 一样,并非每起事故都源于链上逻辑的缺陷。大量损失来自密钥泄露、基础设施受损或受信任的链下服务被攻破。代表性示例:

  • Drift Protocol(约 2.86 亿美元)——签名被攻破,未涉及助记词盗取
  • Step Finance(约 4000 万美元)——签名/金库被攻破
  • Upbit(约 3000 万美元)——中心化交易所;热钱包被攻破
  • SwissBorg(约 4200 万美元)——SwissBorg 应用;第三方质押 API (Kiln) 被攻破

与此同时,许多攻击确实源于 Solana 程序的缺陷。示例:

  • Loopscale(信贷/借贷,约 600 万美元)——价格预言机伪造 / 验证不足
  • Texture Finance(约 200 万美元)——SPL 目标地址伪造 / 验证不足
  • GoonFi(约 25.4 万美元)——AMM 接受了错误的可信价格馈送 / 缺少合理性边界

具体漏洞分析

Solana 的一个架构特征悄然催生了一整类错误。在 EVM 风格的系统中,如果合约已经知道正确的地址,那么从外部传入该地址并验证它通常是多余的——合约可以直接使用自身存储或硬编码在逻辑中的地址。

Solana 的工作方式则不同。程序不会自己去“获取”它的账户;指令将要读取或写入的每一个账户都由交易构建者提供。这不是 API 的便利功能,而是执行模型的核心部分。

这很容易与开发者的常规直觉发生冲突:“用户提供了我们需要的账户,那么我们就用它。”但攻击者的直觉恰恰相反:“如果我提供一个不同的账户会怎样?”因此,Solana 程序必须不断证明它们收到的账户正是协议打算使用的账户。缺少这种证明并非边缘情况,而是最常出现的 Solana 特定漏洞类别之一。

Loopscale 和 Texture 都存在用户提供数据验证不足的问题。GoonFi 则展示了另一种失败模式:即使账户路径没有被攻击者明显伪造,如果程序在没有足够合理性检查的情况下接受了可信的定价状态,它仍然可能不安全。其机制和风险画像各有不同。

关于来源的说明:这些项目的完整链上程序源代码并未公开,这在 Solana 上很常见。以下分析基于链上交易证据、官方事后报告和团队声明,以及从先前网络版本中恢复的客户端代码等辅助材料。

Loopscale

Loopscale 允许用户以抵押品借款。抵押品估值依赖于价格预言机,而在 Solana 上,这些地址不可避免地随交易的账户包传递。对于大多数资产,程序限制了哪些预言机地址是允许的,但在事故前不久,它增加了对 RateX Principal Token 的支持,而该代码路径上的验证不够严格。

攻击者提供了市场价值不到 1 美分的抵押品,接入了一个将其定价为几乎无上限的虚假预言机,从而能够抽走几乎全部可用流动性——利用伪造的抵押品“借出”了所有可访问的资金。

在产品层面,Loopscale 通常在后台组合交易腿,该后台也会过滤预言机地址。但这不是足够的安全控制:攻击者可以在不调用你的 API 的情况下构造恶意指令,并直接将其提交到网络。

协议经验法则:任何会实质性地改变指令结果的输入都必须在链上通过密码学锚定或枚举来约束。如果仍然依赖“可信”的链下组合路径,那么在链上你依然必须证明提供的值符合该路径本应产生的结果——通过明确的允许列表、程序 ID、PDA 等方式(取决于设计)。Loopscale 缺少这种绑定;损失约为 600 万美元。

Texture

这里的漏洞更加直接——而且带有典型的 Solana 风格。一个代码路径从用户提供的账户中获取 LP 铸造的接收方——这在 CPI/账户列表机制下是预期的——但未能强制其必须匹配协议预期的金库。攻击者将 LP 发行路由到任意地址,而不是协议控制的托管账户。

在 Solidity 中,金库地址通常是硬编码或派生得出的,因此只要开发者已经固定了规范目标,最终用户就无法将其替换为恶意接收方。在 Solana 上,账户仍然必须随交易传递,但 Texture 程序未能强制执行“将 LP 铸造到我们的金库”——这是一个价值约 200 万美元的现实提醒,即账户验证应当出现在每一份强制性开发/审计检查清单上。

协议经验法则:每当指令铸造、转移、托管或记入价值时,目标账户是安全不变量的一部分。仅凭账户具有正确的 Mint 或 Token 程序形状是不够的;程序必须证明目标地址是预期的金库、托管、头寸、策略或用户账户。对于协议控制的流程,这通常意味着要将目标地址绑定到存储的配置、PDA 种子、金库权限、Mint 以及市场/策略状态——而不仅仅是接受交易提供的任何 Token 账户。

GoonFi

GoonFi 是一种不同类型的漏洞。它看起来不像“经典”的攻击者驱动漏洞——即获胜交易本身夹带一个虚假账户,程序盲目信任它。受影响的 WSOL/USDC 市场从 GoonFi 相关的外部数据源获取报价,该数据源包含报价的两面——类似买价和卖价的值。

公开来源并未解释该数据源的链下输入来自何处。然而,链上的顺序足够清晰:该数据源原本以约 82.7 发布正常的 SOL/USDC 类值,然后突然写下一个约 2.658 的报价,随后退化为接近零和零。这不是市场变动,而是病态的数据源状态。搜索者看到了机会,从 GoonFi 以远低于市场价的价格买入 WSOL,然后在其他地方平仓。

当前的链上证据并未表明这些搜索者导致了那个错误价格。主要受益者看起来更像是针对公开可见的错误定价进行反应的套利/MEV 账户,而不是数据源崩溃的源头。这一区别很重要:事故仍然耗尽了池子,但失败模式似乎是 AMM 接受了错误的可信定价,而不仅仅是恶意的调用数据。

协议经验法则:如果一个活跃市场可以被一次数据源更新所左右,那么发布者路径和消费程序都需要合理性检查。在同一秒内从约 82.7 跳至约 2.658——超过 30 倍——应触发熔断机制;活跃市场应拒绝接近零或零的报价,除非这些值具有非常明确且安全的含义。

Solana 程序中最常见的问题是什么?

公开事故只是冰山一角。许多关键漏洞在审计中就被发现,从未进入生产环境。审视公开审计报告中最常出现的问题,可以更全面地了解 Solana 程序出错的地方。

Solana 程序中的漏洞可以分为 Solana 特有的和通用的两类。通用问题是审计师在其他链(包括 EVM 生态系统)上也会遇到的类型。这里我们重点讨论 Solana 特有的部分。

一个 Solana 特有的漏洞几乎从不始于一行简单的算术错误。它通常始于对账户图的错误建模。程序接收一组账户:有些签署交易,有些可写,有些是 PDA,有些属于 SPL Token 或 Token-2022,有些被转发到 CPI。如果代码只验证账户的局部形状,而不验证其在整张图中的角色、权限、Mint、所有者、种子、程序 ID、生命周期状态或剩余账户中的动态位置,用户通常可以提交一组“几乎正确”的账户。

这就是为什么最常见的导致高或严重发现的 Solana 特有漏洞类别,看起来像是同一个主题的不同表现形式:程序对账户的信任超出了应有的程度。

Token、Mint、金库与 Token-2022 语义

此类漏洞涉及协议错误建模 SPL Token、Token-2022、Mint/金库关系、ATA(关联 Token 账户)、wSOL/原生 SOL、转账费用、委托、冻结权限或净额与总额核算的情况。

一个典型的真实案例是:再平衡或金库流程检查了 Mint,但没有检查 Token 账户的所有者、权限、金库绑定或 Token 程序。账户局部看起来正确,但价值却流向了协议中的错误角色。

典型形式:

  1. 指令更新了内部核算,但未执行实际的 Token 转移。
  2. 金库、Mint 或抵押品 ID 未绑定到预期的策略或市场。
  3. wSOL/SOL 转换造成了 DoS、租金或关闭账户的边界情况。
  4. 标准的 Token-2022 转账费用或协议层面的主机费导致实际收到的净额与总额不同。
  5. 错误的 Mint 验证允许用错误的 Token 创建池、交易对或头寸。
  6. 费用所有者、金库权限或 Token 账户所有者在错误的位置被检查,或未被完全检查。

如何检查:

  1. 为每个指令构建一个 Token 矩阵:Mint、Token 账户、金库、权限、Token 程序、小数位数、扩展策略和预期所有者。
  2. 不仅要验证 token_account.mint,还要验证关系:金库 -> Mint -> 策略/市场/配置。
  3. 对于 Token-2022,明确列出支持和禁止的扩展。
  4. 测试总额与净额的转移行为:协议费用、主机费用、转账费用、预扣金额以及扩展特定行为。
  5. 单独测试 wSOL/原生 SOL 路径:创建 ATA、同步原生、关闭账户以及租金接收者。
  6. 对于每次转移,比较转移前后的余额与内部核算是否一致。
  7. 对于每个费用账户,验证 Mint、Token 程序、所有者、权限以及其与预期策略、市场或配置的绑定。

账户图完整性

此类漏洞涉及账户之间的错误关系。一个账户可能具有正确的所有者、鉴别器和布局,但仍然可能是错误的对象:其他人的头寸、错误的市场、另一个配置、无关的 tick 数组、虚假的用户状态或错误的债务。

典型形式:

  1. 源和目标头寸可以是同一个账户。
  2. 账户属于正确的程序,但未绑定到所需的 Mint、配置或市场。
  3. 用户状态看起来有效,但并不属于该用户或策略。
  4. 关闭/最终确定指令接受了一个形状正确但来自错误节点共识网络(NCN)、周期或分支的账户。
  5. 重复账户使一个逻辑对象满足了两个本应不同的角色。

如何检查:

  1. 为每个指令构建一个账户矩阵:角色、所有者、鉴别器、种子、签名者/可写标志以及关系检查。
  2. 验证所有隐式关系:position.market == market.key(),vault.mint == config.mint,user_state.owner == signer。
  3. 添加相同形状替换的负面测试:另一个市场、另一个用户、另一个金库、另一个配置。
  4. 当操作不能是自操作时,检查源 != 目标。
  5. 在 Anchor 中,不要仅仅依赖 Account<T>:has_one、constraint、seeds 以及手动关系检查必须覆盖业务图。

信封:畸形数据、恐慌与运行时信封

信封类别是指业务逻辑对于正常输入可能是正确的,但当程序接收到畸形或意外的账户数据时可能会崩溃。在 DeFi 程序审计中,这通常意味着未经检查的反序列化、缺少长度检查、错误的鉴别器、过时的零拷贝布局假设、未经检查的索引、不安全的切片操作,或通过无需权限的指令可达的恐慌路径。

典型形式:

  1. unwrap() 或一个不安全的假设导致恐慌。
  2. 解析器期望某个长度或格式,但在访问前未进行验证。
  3. 接受了一个错误的大小、版本或尾部字节的零拷贝或手动反序列化账户。
  4. DoS 并非来自经济攻击,而是来自任何用户都能触发的异常执行路径。
  5. 代码在快乐路径上稳定,但缺乏针对恶意字节的负面测试语料。

如何检查:

  1. 搜索 unwrap、expect、未经检查的索引、切片、unsafe 和手动反序列化。
  2. 在每次读取账户数据之前,验证长度、鉴别器、所有者、版本和尾部字节策略。
  3. 添加畸形语料测试:短数据、长数据、错误鉴别器、随机字节、重复部分、无效 TLV。
  4. 对于解析器/运行时代码,使用模糊测试和差分测试。
  5. 单独检查恐慌是否可能在无需权限的指令中导致 DoS。

PDA 命名空间、种子与初始化竞争

此类漏洞涉及将确定性地址视为正确逻辑对象证明的错误。PDA 种子定义了命名空间,invoke_signed 可以将 PDA 变为程序权限,生命周期规则决定相同地址是否可以安全地初始化、关闭或重用。如果种子不完整、bump 非规范、PDA 未绑定到父实体,或者允许多个全局配置账户,协议最终可能会产生第二个“有效”对象。

典型形式:

  1. 允许多个全局配置账户。
  2. 缺少 PDA 验证导致重复或未经授权的转移。
  3. 派生地址冲突或命名空间过于宽泛。
  4. PDA 可以被错误的角色关闭或重新初始化。
  5. 种子/权限不匹配导致头寸无法管理或影响提现。

如何检查:

  1. 构建 PDA 注册表:实体、种子、bump 来源、初始化者、关闭权限和签名者使用情况。
  2. 确保种子包含所有必需的域分隔符:协议、市场、Mint、用户、配置、链 ID、nonce。
  3. 为每个 PDA 添加负面测试,使用为另一个用户、市场或配置派生的 PDA。
  4. 检查规范 bump 的使用,以及如果稍后重用 bump 时对存储 bump 的处理。
  5. 测试预初始化、重复初始化、关闭/重新创建以及 init_if_needed 路径。

CPI 信任边界

CPI 类别涵盖程序之间边界处的错误。Solana 的 CPI 不仅传递一个调用,还传递一组 AccountInfo、可写/签名者标志、程序账户,有时还有签名者种子。如果调用者没有使用允许列表和后置检查就信任目标程序或转发的账户,外部程序语义就会成为攻击面的一部分。

一个典型的集成失败是:定价、桥接或路由路径接受由调用者提供的、具有相同接口的外部程序或市场账户。调用成功,但可信事实却来自不可信的程序。

典型形式:

  1. 攻击者可以替换市场、永续合约或 Token 程序账户,并通过 CPI 窃取资产。
  2. 金库 Token 被耗尽,因为 CPI 权限/账户集设置错误。
  3. 部分执行或外部调用使系统处于中间状态。
  4. 跨 CPI 边界发生全链应用(OAPP)或执行者账户重新分配。
  5. 签名者种子或可写账户被转发得比外部调用所需的更为宽泛。

如何检查:

  1. 构建 CPI 映射:目标程序、转发账户、签名者种子、可写账户以及预期的后置条件。
  2. 验证 Token 程序、DEX、预言机、桥接和外部协议的允许列表程序 ID。
  3. 如果外部程序可能已修改账户,则 CPI 后重新加载/重新读取账户。
  4. 最小化转发的可写账户和签名者。
  5. 测试恶意 CPI 目标和相同形状账户替换。

租金、重分配、关闭与残余状态

此类漏洞涵盖 Solana 在 lamports 和账户存储层面的生命周期行为:租金支付者、关闭接收者、重分配大小、陈旧字节、强制创建 ATA、关闭/重新创建。这些错误通常看起来像是小的生命周期细节,但在无需权限的流程中可能转化为租金提取、DoS 或状态混淆。

典型形式:

  1. 强制创建 ATA 允许租金提取。
  2. 关闭指令未验证账户属于所需的上下文。
  3. wSOL 关闭/同步路径破坏了订单流程。
  4. 打包数据长度或重分配逻辑允许了错误的大小。
  5. 自定义程序错误阻止了清理/关闭流程。

如何检查:

  1. 对于每个关闭路径,说明谁可以关闭、lamports 去向何处,以及哪些引用被宣告无效。
  2. 测试关闭后重新创建、以及以相同地址重新创建的流程。
  3. 验证重分配行为:新大小、租金补充/退还以及陈旧字节归零。
  4. 对于 ATA 创建,检查谁支付租金,以及攻击者是否可以强迫他人支付。
  5. 对于 wSOL/原生 SOL,验证同步/关闭/租金接收者策略。

账户所有权与鉴别器

此类漏洞是一个基础但关键的检查:账户必须属于预期的程序,并具有预期的类型/布局。在 Solana 上这并不是装饰性的。外部所有者或错误的鉴别器意味着程序正在读取由另一个信任域创建的字节。

典型形式:

  1. 预言机/范围程序账户未经验证。
  2. 质押池接受了未初始化或瞬态质押账户。
  3. Serum/DEX 市场或预言机账户未检查预期的所有者/类型。
  4. 程序读取了其所有者/鉴别器与预期程序不匹配的状态。

如何检查:

  1. 对于每个账户,显式验证所有者、鉴别器/类型、数据长度以及相关的可执行标志。
  2. 不要仅仅因为账户能反序列化就信任它。
  3. 对于预言机、DEX、范围或外部程序账户,验证程序 ID 以及市场/数据源身份。
  4. 添加测试:使用具有正确布局但错误所有者的账户。
  5. 检查尾部字节和版本策略。

签名者与可写标志语义

此类漏洞涉及谁实际签署了操作以及哪个账户被允许更改的错误。在 Solana 上,签名者标志位于账户元数据上,而权限可能是存储的密钥、PDA 签名者、多重签名、委托或治理地址。这些层次很容易混淆。

典型形式:

  1. 缺少对多重签名签名者的检查。
  2. 多重签名签名者未与指令账户匹配。
  3. 执行者/OAPP 权限可以被重新分配。
  4. 在 Solana 特定的验证流程中绕过了签名。

如何检查:

  1. 对于每个状态变更,写下预期的参与者:用户签名者、管理员签名者、PDA 签名者或多重签名阈值。
  2. 检查签名者账户是否与存储的权限匹配,而不仅仅是它签署了交易。
  3. 对于多重签名,测试缺少签名者、重复签名者、错误顺序、额外签名者以及低于阈值的单个签名者。
  4. 检查可写最小性:不必要的可写账户可能成为攻击面。
  5. 对于 PDA 签名者,验证种子和程序派生权限的来源。

结论

审计 Solana 程序已成为保护 Solana 生态系统中用户资金的重要组成部分。上述讨论的事故和漏洞模式表明,在 Solana 上进行强有力的安全工作需要的不仅仅是普通的 DeFi 经验。这些背景仍然重要,但必须与对 Solana 架构及其协议构建和运行方式的专注理解相结合。对于在 Solana 上构建的团队来说,这使得专业的审计知识不再是锦上添花,而是实际上的必需品。

  • 原文链接: mixbytes.io/blog/solana-...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论