Solana安全检查清单:Anchor和原生程序的45个关键检查项

  • zealynx
  • 发布于 3小时前
  • 阅读 28

本文总结了Solana链上程序开发的五大安全隐患,包括:缺少签名者检查、账户所有权未验证、不安全的跨程序调用(CPI)模式、计算中的整数溢出以及PDA种子冲突。

简而言之 — 导致你的 Solana 程序被黑客攻击的 5 件事

在 Solana 上构建?以下是数百万美元漏洞背后的关键失败之处:

  1. 缺少签名者检查 — 验证 pubkey 而不进行 is_signer 检查,让任何人都可以冒充权威
  2. 账户所有权未验证 — 接受由恶意程序拥有的账户
  3. 不安全的 CPI 模式 — 将用户的签名者转发到不受信任的程序,从而实现钱包盗窃
  4. 计算中的整数溢出 — Release 构建版本默认不检查溢出
  5. PDA seed 冲突 — 用户或功能之间共享 PDA

此清单涵盖 8 个领域中的 45 项安全检查。在审计之前、开发期间以及作为发布前的关卡使用它。


完整安全清单

45+ 个漏洞、攻击模式和缓解措施 — 每一个都带有技术细节和代码示例。专为审计员和具有安全意识的开发团队构建。

→ 查看 Solana 常规安全清单


简介:为什么 Solana 安全性是独一无二的

Solana 的编程模型与基于 EVM 的链根本不同。当以太坊开发者担心重入和存储冲突时,Solana 开发者面临着一系列独特的挑战:

  • 基于账户的架构 — 每条状态都是一个必须验证的账户
  • 显式签名者 — 你必须同时检查 pubkey 和签名状态
  • 跨程序调用 — CPI 转发签名者,创造了新的攻击媒介
  • Token-2022 扩展 — 新的 Token 功能需要新的安全考虑

风险很高:

  • 3.26 亿美元 —虫洞(2022 年 2 月)— 缺少签名者验证
  • 1.15 亿美元 — Mango Markets(2022 年 10 月)— 价格操纵
  • 4800 万美元 — Cashio(2022 年 3 月)— 缺少账户验证

此清单是从 50 多份 Solana 审计报告和每个主要的 Solana 漏洞中提炼出来的。无论你使用 Anchor 还是原生 Solana 构建,本指南都涵盖了重要的漏洞。


如何使用此清单

此清单分为八个关键领域:

  1. 身份验证和授权
  2. 状态管理
  3. 跨程序调用 (CPI)
  4. 数学和精度
  5. Token 操作
  6. Token-2022 扩展
  7. 边缘情况和陷阱
  8. 高级问题

每个检查都包括严重性评级(严重/高/中)以及我们交互式清单中详细说明的链接。


1. 身份验证和授权

为什么重要

Wormhole 漏洞(3.2 亿美元)的发生是因为程序在没有验证 is_signer 标志的情况下检查了 pubkey。身份验证失败是 Solana 漏洞的头号原因。

安全检查

账户数据匹配(严重)

  • [ ] 存储的授权与签名者匹配 — 所有特权功能都验证签名者的 pubkey 是否与存储的授权匹配
  • [ ] 使用 Anchor 约束 — 首选 constraint = config.admin == admin.key() 而不是手动检查
  • [ ] 在状态更改之前检查 — 在任何状态修改之前验证授权

缺少签名者检查(严重)

  • [ ] 始终验证 is_signer — 仅检查 pubkey 是不够的;任何人都可以传递任何 pubkey
  • [ ] 使用 Anchor 的 Signer<'info> — 此类型会自动验证签名
  • [ ] 在 pubkey 之前检查签名者 — 顺序对于安全性的明确性很重要

缺少所有权检查(严重)

  • [ ] 验证账户所有者 — 确保账户由预期的程序拥有
  • [ ] 使用 Account<'info, T> — Anchor 会自动验证类型化账户的所有权
  • [ ] 使用 seeds/bump 检查 PDA — PDA 所有权通过 seed 推导隐式地确定

权限转移(高)

  • [ ] 实施两步转移 — 提名 → 接受模式可防止意外锁定
  • [ ] 验证新权限 — 检查它是否为零/默认地址
  • [ ] 允许取消 — 当前权限应能够取消待处理的转移

不安全的初始化(严重)

  • [ ] 限制初始化程序 — 仅允许程序升级权限或硬编码的部署者
  • [ ] 使用 PDA init 约束 — 防止重新初始化
  • [ ] 验证升级权限 — 检查 program_data.upgrade_authority_address

危险信号

  • 仅检查 admin.key() == expected_key 而没有 Signer<'info> 的函数

  • 没有所有权约束的 AccountInfo<'info>UncheckedAccount

  • 单步权限转移

  • 任何人都可以调用的初始化

    • *

2. 状态管理

为什么重要

Solana 的账户模型意味着状态可以以以太坊开发者意想不到的方式进行操纵。陈旧的数据、重复的账户和不正确的关闭都会产生漏洞。

安全检查

账户数据重新分配(中)

  • [ ] 使用正确的 zero_init 参数 — 当大小在同一事务中减小后增加时,设置为 true
  • [ ] 限制动态数据大小 — 防止可能耗尽租金的无限制增长
  • [ ] 考虑固定大小的账户 — 尽可能避免重新分配的复杂性

账户重新加载(高)

  • [ ] 在 CPI 之后重新加载 — 在读取之前,对 CPI 修改的账户调用 .reload()
  • [ ] 不要缓存陈旧数据 — 新鲜读取比缓存值更安全
  • [ ] 记录 CPI 影响 — 跟踪可能修改哪些账户

关闭账户(高)

  • [ ] 关闭时将所有数据归零 — 防止使用陈旧数据进行复活攻击
  • [ ] 设置 CLOSED_ACCOUNT_DISCRIMINATOR — Anchor 的 close 约束可以做到这一点
  • [ ] 转移所有 lamports — 不要使账户免受租金的影响,但却为空

重复的可变账户(高)

  • [ ] 添加唯一性约束 — constraint = account_a.key() != account_b.key()
  • [ ] 使用不同的类型 — 类型安全可防止同一账户用于不同的目的
  • [ ] 使用重复项进行测试 — 验证是否拒绝两次传递的同一账户

PDA 共享(高)

  • [ ] 在 seed 中包含用户 pubkey — 每个用户都应该有自己的 PDA
  • [ ] 使用不同的前缀 — 不同的账户类型需要不同的 seed 前缀
  • [ ] 记录隔离模型 — 此 PDA 是全局的还是每个用户的?

Bump Seed 规范化(中)

  • [ ] 永远不要接受用户提供的 bumps — 始终以规范方式推导
  • [ ] 使用 Anchor 的 bump 约束 — 自动规范派生
  • [ ] 存储和重用 bumps — 将规范 bump 保存在账户数据中

危险信号

  • 具有硬编码 zero_init = falserealloc

  • CPI 之后访问的账户数据没有重新加载

  • 仅转移 lamport(没有数据归零)的关闭账户

  • 仅具有静态 seed(没有用户区分)的 PDA

    • *

3. 跨程序调用(CPI)

为什么重要

CPI 功能强大但危险。将签名者转发到不受信任的程序可能会导致钱包被盗或所有权重新分配。“盗取钱包”攻击可能会永久锁定用户资金。

安全检查

任意 CPI(严重)

  • [ ] 验证目标程序 ID — 永远不要在未经验证的情况下 CPI 到用户提供的程序
  • [ ] 使用 Program<'info, T> — Anchor 会自动验证程序 ID
  • [ ] 将可信程序列入白名单 — 仅 CPI 到已知、经过审计的程序

CPI 签名者陷阱(严重)

  • [ ] 不要转发用户钱包 — 而是使用协议 PDA 作为权限
  • [ ] 在 CPI 之后检查所有权 — 验证账户是否未被重新分配
  • [ ] 保护 lamport 余额 — 检查转发的签名者之前/之后的余额

安全依赖链(高)

  • [ ] 验证所有依赖项 — 如果账户 A 依赖于账户 B,则验证 B
  • [ ] 没有不受约束的来源 — 验证链中的每个账户都需要约束
  • [ ] 记录依赖关系图 — 映射哪些验证依赖于什么

Lamport 余额不匹配(中)

  • [ ] 在 CPI 之后检查余额 — CPI 可能会意外修改 lamport
  • [ ] 考虑租金 — 免租金最小值可能会导致意外
  • [ ] 尽可能使用拉取模式 — 比推送 lamport 更安全

危险信号

  • 使用用户提供的程序账户的 invoke()

  • 传递给第三方程序 CPI 的用户钱包 (Signer<'info>)

  • 具有不受约束的根账户的验证链

  • 接收签名者的 CPI 之后没有所有权检查

    • *

4. 数学和精度

为什么重要

Rust 的 release 构建版本(Solana 使用的版本)默认不检查整数溢出。单个溢出可能导致攻击者铸造无限 Token 或耗尽协议。

安全检查

溢出/下溢(严重)

  • [ ] 使用 checked arithmetic — checked_addchecked_subchecked_mulchecked_div
  • [ ] 启用溢出检查 — 在 Cargo.toml 中设置 overflow-checks = true
  • [ ] 永远不要对值使用 wrapping arithmetic — 仅用于有意的包装

精度损失(高)

  • [ ] 先乘后除 — (a * b) / c 而不是 (a / c) * b
  • [ ] 以协议的名义取整 — 付款时向下取整,收费时向上取整
  • [ ] 对中间值使用 u128 — 扩大规模以防止中间溢出

除以零(高)

  • [ ] 检查除数 — 在任何除法之前验证非零
  • [ ] 处理空池 — 流动性/供应为零时的特殊情况
  • [ ] 使用 checked_div — 返回 None 而不是 panic

类型转换漏洞(高)

  • [ ] 对收窄使用 try_fromu64::try_from(value) 而不是 value as u64
  • [ ] 在转换之前验证符号 — 在转换为 u64 之前检查 i64 >= 0
  • [ ] 避免对用户输入使用 as — 静默截断很危险

危险信号

  • u64 值上的直接算术运算符 (+-*/)

  • 对用户提供或计算的值进行 as 转换

  • 没有零检查的除法

  • 收费计算中除法后的乘法

    • *

5. Token 操作

为什么重要

Token 处理需要仔细验证。错误的 mint、丢失的权限以及收费转移 Token 都会产生漏洞。

安全检查

与 Token 无关的接口(中)

  • [ ] 使用 token_interface — 为了 Token-2022 兼容性
  • [ ] 使用 transfer_checked — 验证小数,适用于两个程序
  • [ ] 不要混合类型 — 将 Interface<'info, TokenInterface>token_interface::transfer_checked 结合使用

预先创建的 ATA(中)

  • [ ] 使用 init_if_needed — ATA 可以由任何人预先创建
  • [ ] 永远不要对 ATA 使用 init — 攻击者可以抢先并进行 DoS 攻击
  • [ ] 使用现有的 ATA 进行测试 — 验证你的程序是否可以处理这两种情况

SPL Token 验证(高)

  • [ ] 验证 mint — token_account.mint == expected_mint
  • [ ] 验证所有者/权限 — token_account.owner == expected_owner
  • [ ] 检查冻结的账户 — 某些操作在冻结的账户上会失败

危险信号

  • 关联 Token 账户上的 init 约束

  • 没有 mint 验证的 Token 转移

  • token::transferInterface<'info, TokenInterface> 结合使用

    • *

6. Token-2022 扩展

为什么重要

Token-2022 引入了强大的扩展,可以从根本上改变 Token 行为。没有考虑到这些功能的程序可能会被利用。

安全检查

CPIGuard 扩展(高)

  • [ ] 处理 CPI 限制 — 某些账户无法通过 CPI 转移
  • [ ] 检查扩展状态 — 在 CPI 之前检测是否启用了 CPIGuard
  • [ ] 提供回退流程 — 受保护账户的替代路径

默认账户状态(中)

  • [ ] 检查初始冻结状态 — 默认情况下,新账户可能会被冻结
  • [ ] 处理解冻要求 — 在使用前可能需要权限来解冻
  • [ ] 验证 mint 配置 — 检查 default_account_state 扩展

Mint Close 权限(高)

  • [ ] 验证 mint 是否存在 — Mint 可以在 Token-2022 中关闭
  • [ ] 在操作之前检查 — 不要假设 mint 是永久的
  • [ ] 妥善处理已关闭的 mint — Token 变得毫无价值

永久委托(严重)

  • [ ] 检查永久委托 — 他们可以从任何账户转移/销毁
  • [ ] 警告用户风险 — 具有永久委托的存款可能会被收回
  • [ ] 考虑阻止存款 — 对于高安全性金库

Transfer Hook(高)

  • [ ] 预算额外的 CUs — Hook 会消耗计算单元
  • [ ] 处理 Hook 失败 — 转移可能会因 Hook 逻辑而失败
  • [ ] 使用启用 Hook 的 Token 进行测试 — 验证你的程序是否有效

Transfer Fees(高)

  • [ ] 在计算中考虑费用 — 接收者收到的金额少于发送的金额
  • [ ] 使用 transfer_checked — 自动处理费用计算
  • [ ] 验证预期金额 — 检查实际收到的金额与预期金额

危险信号

  • 没有检查 Token-2022 扩展

  • 假设转移金额等于收到的金额

  • 没有为转移 Hook 预算 CU

  • 接受具有永久委托的 Token 存款

    • *

7. 边缘情况和陷阱

为什么重要

细微的错误和 Rust 语法错误可能会导致严重的漏洞。这些问题通常会通过代码审查,但在生产中会失败。

安全检查

抢先交易(高)

  • [ ] 需要预期值 — 所有交易的滑点保护
  • [ ] 使用提交-揭示 — 用于敏感操作
  • [ ] 包括截止日期 — 拒绝过时的交易

Lamport 转移 Kill Switch(中)

  • [ ] 使用拉取模式 — 让用户提款而不是推送退款
  • [ ] 验证租金豁免 — 转移到 0-lamport 账户可能会失败
  • [ ] 检查是否不可执行 — 无法将 lamport 转移到程序

向量长度问题(中)

  • [ ] 使用 vec![value; count] — 而不是 vec![count]
  • [ ] 首选 push/extend — 比索引赋值更安全
  • [ ] 使用各种大小进行测试 — 在测试时捕获越界

类型角色扮演(高)

  • [ ] 验证鉴别器 — Anchor 会自动为 Account<'info, T> 执行此操作
  • [ ] 不要信任原始 AccountInfo — 可能是任何账户类型
  • [ ] 检查程序所有权 — 即使具有正确的鉴别器也是如此

Sysvar 地址检查(中)

  • [ ] 验证 sysvar 地址 — 使用 address = sysvar::xxx::ID 约束
  • [ ] 不要信任用户提供的 sysvar — 可能是虚假数据
  • [ ] 使用 Anchor 的 sysvar 类型 — 自动验证

剩余账户(中)

  • [ ] 验证每个剩余账户 — 没有自动检查
  • [ ] 检查所有权和数据 — 需要手动验证
  • [ ] 记录预期格式 — 哪些剩余账户有效

不安全的 Rust(高)

  • [ ] 最小化不安全块 — 仅在绝对必要时
  • [ ] 验证所有输入 — 不安全并不意味着未检查
  • [ ] 记录不安全理由 — 为什么需要且安全

Seed 冲突(高)

  • [ ] 使用唯一前缀 — b"user_vault"b"admin_vault"
  • [ ] 包括区分数据 — 用户 pubkey、ID 等
  • [ ] 测试冲突场景 — 验证账户是否无法重叠

危险信号

  • 交易上没有滑点保护

  • 直接将 lamport 转移到用户提供的地址

  • vec![N] 意为 vec![0; N]

  • 没有验证逻辑的 UncheckedAccount

    • *

8. 高级问题

为什么重要

这些问题需要对 Solana 内部原理有更深入的了解,即使是经验丰富的开发人员也经常忽略。

安全检查

悬空指针(高)

  • [ ] 小心关闭的账户 — 对关闭的账户的引用无效
  • [ ] 不要跨指令持有引用 — 账户数据可能会更改
  • [ ] 不确定时重新加载 — 新鲜读取更安全

账户重新分配错误(中)

  • [ ] 避免临时重新分配 — 更改所有者然后改回来是有风险的
  • [ ] 数据可能会被擦除 — 临时所有者可能会修改数据
  • [ ] 记录所有权生命周期 — 何时以及为什么所有者会更改

堆耗尽(中)

  • [ ] 限制循环迭代 — 没有无限制的循环
  • [ ] 限制分配 — 32KB 堆限制
  • [ ] 尽可能使用堆栈 — 比堆便宜

账户约束脆弱性(中)

  • [ ] 测试边缘情况 — 零值、最大值、相同账户
  • [ ] 验证约束逻辑 — 约束可能存在细微的错误
  • [ ] 使用显式检查 — 当约束变得复杂时

Ed25519 内省(高)

  • [ ] 验证指令位置 — 签名必须位于预期的索引处
  • [ ] 验证所有元数据 — Pubkey、消息和签名
  • [ ] 防止签名重用 — 不同的上下文不应共享签名

危险信号

  • 跨关闭账户的 CPI 持有的引用

  • 没有明确目的的临时所有者更改

  • 无限制的向量或循环

  • 没有位置验证的 Ed25519 签名

    • *

结论:安全是一个过程

此清单是一个起点,而不是终点。Solana 安全性需要:

  1. 持续学习 — 不断出现新的模式和攻击
  2. 专业审计 — 清单不能代替专家审查
  3. 深度防御 — 多层保护
  4. 事件响应 — 出现问题时的计划(而不是如果)

获得专业的 Solana 审计

我们的团队已审计了 30 多个 Solana 协议,在关键漏洞变成漏洞之前发现并修复了它们。

我们提供什么:

  • 在 Anchor 和原生 Solana 方面的深厚专业知识
  • Token-2022 扩展安全性分析
  • CPI 安全性和可组合性审查
  • 包含修复验证的综合审计报告

→ 请求 Solana 审计报价


附加资源

  • 原文链接: zealynx.io/blogs/solana-...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
zealynx
zealynx
江湖只有他的大名,没有他的介绍。