保护 Solana 生态系统(四)——账户验证

文章讨论了 Solana 编程环境下的访问控制相关问题,重点关注了账户正确验证的重要性以及潜在的安全风险。

保护 Solana 生态系统(四)——账户验证

0. 回顾

1. 概述

在上一篇博客中,我们讨论了如何升级程序。在本篇文章中,我们将介绍访问控制相关的安全问题,这是 DeFi 领域中最常见也是最基础的安全话题之一。

2. 说明

在 Solana 中,每个程序都导出一个单独的 entrypoint,它由 entrypoint! 定义。与以太坊不同,客户端只能调用一个作为入口点的函数,通常称为 process_instruction。入口点函数接收三个参数:智能合约的程序 ID、程序将要操作的账户以及指令数据。指令数据指定了将要调用的指令。下图展示了一个示例。通过解包指令数据,可以选择不同的指令(例如,Lock,Unlock)。因此,从入口点可以访问的指令对所有人都是公开的,并且可以使用指定的指令数据来执行。

3. 账户验证

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

3.1 代码审查 (PrivilegeOwner)

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

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

指令 Open() 用于打开门。该指令接收几个账户,包括要打开的门账户、config 账户以及旨在打开 doorowner 账户。为了保证门属于程序且配置有效,我们检查 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 中,指令根据客户端或其他程序提供的不同账户实现特定的逻辑。因此,对账户进行适当的检查非常重要。

在本文中,我们介绍了如何正确检查账户,并通过两个攻击场景来说明这些检查的重要性。请继续关注,我们将分享更多文章。

阅读本系列的其他文章:


关于 BlockSec

BlockSec 是一家开创性的区块链安全公司,由一群全球杰出的安全专家于 2021 年创立。公司致力于增强新兴 Web3 世界的安全性和可用性,以促进其大规模采用。为此,BlockSec 提供智能合约和 EVM 链安全审计服务、用于安全开发和主动阻止威胁的Phalcon平台、用于资金追踪和调查的MetaSleuth平台,以及用于 Web3 开发者高效浏览加密世界的MetaSuites扩展。

迄今为止,公司已为 MetaMask、Uniswap Foundation、Compound、Forta 和 PancakeSwap 等 300 多家知名客户提供服务,并从 Matrix Partners、Vitalbridge Capital 和 Fenbushi Capital 等知名投资者那里获得了两轮数千万美元的融资。

官方网站:https://blocksec.com/

官方 Twitter 账号:https://twitter.com/BlockSecTeam

Sign up for the latest updates