Back to Blog

保障Solana生态系统安全(4)— 账户验证

April 1, 2022
6 min read

0. 回顾

1. 概述

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

2. 指令

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

3. 账户验证

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

3.1 代码审查(PrivilegeOwner)

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

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

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

在函数 validate_owner() 中,我们首先检查这两个账户的公钥是否相同,然后检查所有者的签名。这回答了第二个问题——要保证只有具有权限的用户才能调用 open 指令,我们需要检查账户的所有者和签名者。close 指令与 open 类似,详细信息可在代码中找到。

我们已将该程序部署在测试网上,可通过以下链接查看。

https://explorer.solana.com/address/2Q7FFMWCthBvc6ubLQRx9TRswvaimmd66VaCAfHwsYuC?cluster=testnet

所有测试交易如下所示。此交易的完整流程为 AllocatePDA()-> 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 行)。

结果如下所示,日志打印出 incorrect program id for instruction,这意味着 config 账户的所有者必须是该程序。因此,攻击者无法绕过此检查。

第二种场景

第二种场景是恶意用户在门未锁定时尝试打开门。

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

日志打印出 Signature verification failed,这意味着真正的所有者必须对交易进行签名才能打开门,因此我们的第二次攻击同样失败。

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