0. 回顾
- Securing the Solana Ecosystem (1) — Hello Solana
- Securing the Solana Ecosystem (2) — Calling Between Programs
- Securing the Solana Ecosystem (3) — Program Upgrade
1. 概述
在上一篇博客中,我们讨论了如何升级程序。在本篇文章中,我们将介绍访问控制相关的安全问题,这是 DeFi 领域中最常见也是最基础的安全话题之一。
2. 说明
在 Solana 中,每个程序都导出一个单独的 entrypoint,它由 entrypoint! 定义。与以太坊不同,客户端只能调用一个作为入口点的函数,通常称为 process_instruction。入口点函数接收三个参数:智能合约的程序 ID、程序将要操作的账户以及指令数据。指令数据指定了将要调用的指令。下图展示了一个示例。通过解包指令数据,可以选择不同的指令(例如,Lock,Unlock)。因此,从入口点可以访问的指令对所有人都是公开的,并且可以使用指定的指令数据来执行。

3. 账户验证
如前所述,程序接收其需要读取或写入的账户。这种设计带来了两个问题。对于需要读取的账户,如何保证账户中存储的数据是可信的?对于需要写入的账户,如何保证只有特权用户才能调用指令进行写入?接下来,我们将阐述访问控制问题。所有测试代码均可在此处找到。
3.1 代码审查 (PrivilegeOwner)


我们首先定义两个结构体:Door 和 Config。只有在 door 结构体中指定的密钥账户(第 17 行)才能打开创建的 door。但是,当系统状态被锁定(在 Config 结构体中指定,第 81 行)时,门无法被打开。

如前所述,Config 账户指定了门是否可以被打开。在这种情况下,程序中应该只有一个 Config 账户。为了实现这一点,我们使用 PDA 来存储 Config 的数据。初始化 Config 账户后,我们将 is_initialized 属性设置为 true,以便攻击者无法再次初始化它(第 108-110 行)。

指令 Open() 用于打开门。该指令接收几个账户,包括要打开的门账户、config 账户以及旨在打开 door 的 owner 账户。为了保证门属于程序且配置有效,我们检查 door 账户和 config 账户的所有者(第 204-205 行)。这可以防止恶意用户提供虚假账户。这回答了我们的第一个问题。为了保证要读取的账户是可信的,我们需要检查账户的所有者!。请注意,只有 door 账户的所有者才能打开门。在这种情况下,我们检查所有者账户是否是 door 的真实 owner,更重要的是,该指令是否由所有者授权(第 217-219 行)。

在 validate_owner() 函数中,我们首先检查这两个账户的公钥是否相同,然后检查所有者的签名。这回答了第二个问题:为了保证只有特权用户才能调用 open 指令,我们需要检查账户的所有者和签名者。close 指令与 open 类似,详细信息请参见代码。
我们在测试网上部署了该程序,可以通过以下链接找到它。
https://explorer.solana.com/address/2Q7FFMWCthBvc6ubLQRx9TRswvaimmd66VaCAfHwsYuC?cluster=testnet
以下是所有测试交易。整个交易过程为 Allocate PDA() -> InitializeDoor() -> InitializeConfig() -> Unlock() -> Open() -> Close()。
https://explorer.solana.com/tx/2X9CyMrHTNEvbzXTE95gem2j8spnvsQsabFeSpV8hiNpYjiQPPzLRqt5KN86ZYRjnQvydvs7y5eUjJK7no8knDhk?cluster=testnet
https://explorer.solana.com/tx/2XfVWiXeQeHbpqAEYm3AH2RU6hunnqtr155EC4EAM5Bq9VVZNP6QocAav9cPjEQdJFcQrbsSSxiKadr4HPMov8pz?cluster=testnet
https://explorer.solana.com/tx/5Em41sg7yFXeNpnEJnhUQJanfLWKwjMqiBeNAqEEzFrSN9P8zKKafcv5F7RKT2pseB171qeoa8Uz4fKgazzayCnW?cluster=testnet
https://explorer.solana.com/tx/2PMtzpSgjnKDLGmRWBdUSFBPimWnudCPekUYbWzPzokENFYa4N4ab4HCtynfGrzswFPTgGYKHU8PccUMHv3mXHkR?cluster=testnet
https://explorer.solana.com/tx/3kviP9MqkWGMV4yA7k7yPQ5BGfXmcYLcctmY1u2D7n56eT1nx8jMtDumkUNJy8yA3KkmzrmfQLjqpigc8ehGZzBN?cluster=testnet
https://explorer.solana.com/tx/38iEaJBzuGMLbfcszdVB8pkniezH8JrA3XGq7JdADZTQ4hNQC82GSTUA2bmcypdVy3t7htWnUzkZ4F8EakmNvqz8?cluster=testnet
3.2 攻击交易
为了说明所有者检查和签名者检查的重要性,我们使用两个攻击场景作为示例。
第一种场景
第一种是“门”的所有者在 config 被锁定期间尝试打开门。为了实现这一点,我们在另一个程序中创建了一个假的 config 账户,并将其 is_lock 属性设置为 false。自定义程序的代码如下所示。
我们发送交易来创建假的 config 账户,假 config 账户的公钥为:2MtSrbWp24VjPZQcSUkiWrvNro7qqKemVCsh3Yxc8LTy。
https://explorer.solana.com/tx/2qSyrL5gdQXmgGCFzmzMm1StFQRkDgWpss9A9jV11q2fgDGM5C1XRuXvbX1N5Dt3q2pRqnmyXHVtXGF5dqadAzpJ?cluster=testnet
一旦创建了假的 config 账户,我们就将其传入程序(第 423 行)。

结果如下所示,日志打印“指令的程序 ID 不正确”,这意味着 config 账户的所有者必须是程序本身。因此,攻击者无法绕过此检查。

第二种场景
第二种场景是一个恶意用户试图在门未锁定的情况下打开门。

在这种情况下,我们将真实的所有者账户传入程序(第 419 行)并发送交易。结果如下所示。

它打印“签名验证失败”,这意味着真实的所有者必须签名才能打开门,所以我们的第二次攻击也失败了。
4. 总结
在 Solana 中,指令根据客户端或其他程序提供的不同账户实现特定的逻辑。因此,对账户进行适当的检查非常重要。
在本文中,我们介绍了如何正确检查账户,并通过两个攻击场景来说明这些检查的重要性。请继续关注,我们将分享更多文章。
阅读本系列的其他文章:
- Securing the Solana Ecosystem (1) — Hello Solana
- Securing the Solana Ecosystem (2) — Calling Between Programs
- Securing the Solana Ecosystem (3) — Program Upgrade
- Securing the Solana Ecosystem (5) — Multi-Sig
- Securing the Solana Ecosystem (6) — Multi-Sig2
- Securing the Solana Ecosystem (7) — Type Confusion
关于 BlockSec
BlockSec 是一家开创性的区块链安全公司,由一群全球杰出的安全专家于 2021 年创立。公司致力于增强新兴 Web3 世界的安全性和可用性,以促进其大规模采用。为此,BlockSec 提供智能合约和 EVM 链安全审计服务、用于安全开发和主动阻止威胁的Phalcon平台、用于资金追踪和调查的MetaSleuth平台,以及用于 Web3 开发者高效浏览加密世界的MetaSuites扩展。
迄今为止,公司已为 MetaMask、Uniswap Foundation、Compound、Forta 和 PancakeSwap 等 300 多家知名客户提供服务,并从 Matrix Partners、Vitalbridge Capital 和 Fenbushi Capital 等知名投资者那里获得了两轮数千万美元的融资。
官方 Twitter 账号:https://twitter.com/BlockSecTeam




