0. 回顾
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/2fVWiXeQeHbpqAEYm3AH2RU6hunnqtr155EC4EAM5Bq9VVZNP6QocAav9cPjEQdJFcQrbsSSxiKadr4HPMov8pz?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 中,指令根据客户端或程序提供的不同账户来实现特定的逻辑。因此,对账户进行适当的检查非常重要。
在本文中,我们介绍了如何正确检查账户,并通过两个攻击场景来说明这些检查的重要性。请继续关注,我们将分享更多文章。
阅读本系列的其他文章:
- 保护 Solana 生态系统 (1) — Hello Solana
- 保护 Solana 生态系统 (2) — 程序间调用
- 保护 Solana 生态系统 (3) — 程序升级
- 保护 Solana 生态系统 (5) — 多重签名
- 保护 Solana 生态系统 (6) — 多重签名 2
- 保护 Solana 生态系统 (7) — 类型混淆
关于 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




