本文深入探讨了 Solana Anchor 框架中隐藏的 IDL(接口描述语言)指令及其潜在的安全风险。
Solana 安全研究,由 accretion.xyz 提供
我们描述了 Solana Anchor 如何将 IDL 指令与你的程序捆绑在一起,以及这会带来哪些安全隐患
看看下面的代码。这个程序有多少条指令?
lib.rs
Cargo.toml
显而易见的答案是 1 条指令:initialize
。
但如果你回答 1,那就错了。实际上,正确的答案是 8 条指令。
为什么?因为 Anchor 向你的程序添加了 7 条指令,以启用 IDL 上传。许多开发者并不知道,此功能默认是开启的。
什么是 IDL 上传? IDL(接口描述语言)是一个 JSON 文件,描述了用户如何与 Solana 程序交互。它描述了程序实现的各种指令,以及调用它们所需的参数和账户。
理论上,这个 JSON 文件可以存储在任何地方。但是,我们拥有区块链是为了什么?将 IDL 存储在链上每个程序的预定地址上,这才有意义。
因此,Anchor 实现了一个功能,可以将 IDL 文件上传到链上账户,该账户从程序地址和种子 " anchor:idl
" 派生而来。这样,用户可以通过从程序地址计算此账户地址来轻松找到程序 IDL。
现在你了解了我们所说的内容,让我们看看这个 IDL 上传功能是如何实现的。正如我之前暗示的那样,有 7 个“隐藏”指令被添加到 Anchor 程序中:
IdlCreateAccount ( 免许可,一次性) 在 anchor:idl 种子处创建主 IDL 账户。任何人都可以调用,且只能调用一次。IDL 授权者将是调用此指令的人。
IdlResizeAccount ( IDL 授权者) 调整 IDL 账户的大小
IdlCloseAccount ( IDL 授权者) 关闭 IDL 账户
IdlCreateBuffer ( 免许可) 使用预先分配的系统账户创建 IDL 缓冲区账户
IdlWrite ( IDL 授权者) 写入 IDL 账户
IdlSetAuthority ( IDL 授权者) 更改 IDL 账户的授权者
IdlSetBuffer ( IDL 授权者) 将数据从 IDL 缓冲区账户复制到主 IDL 账户
除了这些指令之外,还有一种新的账户类型 IdlAccount
,它有三个字段:authority
、data_len
和 data
:
IdlAccount 结构。data 字段在代码库中没有显式声明
虽然可以有很多这种类型的账户,但只有一个主要的 IdlAccount
,它包含 IDL。它的地址从种子 " anchor:idl
" 派生而来。除了主要的 IdlAccount
之外,还可以有任意的 IDL 缓冲区账户,它们是位于任意地址的 IdlAccount
。其思想是,可以将新的 IDL 分块上传到缓冲区账户,然后可以一步更新主要的 IdlAccount
。
在了解了此过程之后,这 7 条指令确实有意义:IdlCreateAccount
用于初始化主要的 IdlAccount
,而 IdlResizeAccount
用于重新分配它。IdlCreateBuffer
用于创建非主要的 IdlAccount
,称为缓冲区,而 IdlCloseAccount
用于关闭缓冲区,甚至可以关闭主要的 IdlAccount
。IdlWrite
将一个块写入缓冲区,而 IdlSetAuthority
更改 IdlAccount
的授权者。最后,IdlSetBuffer
将缓冲区复制到主 IdlAccount
。
它是完全管理 IDL 上传系统(包括缓冲区和授权管理)的最小指令集。而且,这在很大程度上是没问题的。
但是,如果没有什么大问题,我就不会发表这篇文章:这两个免许可函数,IdlCreateAccount
和 IdlCreateBuffer
。
是的,你没看错。IdlCreateAccount
,用于创建一个程序的主 IDL 账户,并分配其 IDL 权限的函数,是 免许可的。任何人都可以调用它,并将自己设置为该程序 IDL 的主人。问题在于,它只能被调用一次。这意味着,在理想情况下,开发者会上上传他们的合约,并立即调用此指令来设置 IDL 账户。但现实并非如此。Solana 主网上有无数程序启用了 Anchor 的 IDL 上传功能(这是默认设置),但从未调用过此指令。这对受影响的项目意味着什么?这意味着攻击者可以阻止这些项目上传 IDL,更糟糕的是,攻击者可以上传恶意 IDL,用于欺骗任何尝试使用链上 IDL 的人。(请在我们的下一篇博文中阅读更多关于恶意 IDL 攻击的信息)。我称这种类型的攻击为 IDL 接管攻击。请注意,这并不是一个新问题,事实上,Anchor 开发者已经知道了好几年了。
Github issue 讨论了 2021 年的 IDL 权限改进
https://github.com/coral-xyz/anchor/issues/444#issuecomment-957056279
这种攻击的另一个版本只是攻击前端工具,例如浏览器,以显示不正确的信息。例如,浏览器可以使用链上 IDL 来确定某个指令签名被称为“ deposit”,因此它会将该指令显示为“ deposit”指令。但是,恶意 IDL 可能会将显示的名称更改为“ accretion.xyz”,并在有人查看涉及此指令的交易时生成免费广告。我为自己的 Ore 挖矿程序生成了这样一个虚假的 IDL。结果是:一个应该显示为“ Mine”的指令显示为“ Accretion.xyz”。
我的程序在官方 Solana 浏览器中将一个指令显示为 Accretion.xyz,即使它应该是“Mine”
(另外,我已将上面 IDL 中的第一个账户重命名为“ The Boss”而不是“Authority”)。为了证明我的观点,我从一个与程序完全无关且没有特殊权限的账户上传了此 IDL。任何人都可以让浏览器显示我程序的任何标签。
我认为 稍微危险 的第二个指令是 IdlCreateBuffer
。它本身是无害的。但它为一个强大的原语提供支持,用于在 Solana 程序中利用类型混淆(有时称为类型角色扮演)攻击。先决条件是你的程序有另一个容易受到类型混淆攻击的指令。这意味着另一个易受攻击的指令在加载账户时,不检查账户的鉴别器,只检查账户所有者。只有当我们有另一个可以合法地作为另一个账户类型传递给程序的账户时,这样的漏洞通常才是可利用的。这意味着它应该具有理想情况下相同的长度和任意可控的数据。
IdlCreateBuffer
允许任何人创建由你的程序拥有的账户,这些账户的内容几乎完全是任意的,并且账户长度几乎是任意的。记住 IDL 账户的布局,我们可以看到攻击者几乎完全控制了该内容。他们可以将权限设置为任意字节,并且数据也是任意的。只有 data_len
字段必须是以下数据的实际长度,并且不能完全是任意的。
可以创建一个 IdlAccount,其中包含几乎完全任意的数据,并且长度是任意的,仅受最小值的限制
现在想象一下,我们有一个易受攻击的函数,它期望给定的账户具有以下布局
一个假想的账户类型,我们将尝试扮演它
易受攻击的函数会加载这个账户,检查账户所有者是否是程序,以及程序的尺寸是否正确,但不检查账户鉴别器。要利用这一点,攻击者需要在同一个程序中拥有另一个相同长度的账户类型,并且能够控制字段内容,这种情况很少见。但是,使用 IdlCreateBuffer
,攻击就变得可能了。
让我们看看我们将如何利用它:
创建一个缓冲区账户。首先,攻击者会创建一个新账户,大小为 84 字节,使用 IdlCreateBuffer
对其进行初始化,并将自己设置为此缓冲区的权限。由于账户大小为 84 字节,因此 data_len
将为 84 减去鉴别器长度和权限长度,因此 data_len
= 44 或 0x0000002c。此时的数据将只是零。该账户将如下所示:
一个新创建的 IdlAccount 缓冲区,正确解码为 IdlAccount
将同一账户重新解释为 DepositReceipt
,它变为
同一个新创建的 IdlAccount 缓冲区,解码为存款凭证
接下来,我们继续为 100 万美元的 USDC 创建一个虚假的存款凭证。为此,我们调用 IdlSetData
函数,并将数据设置为 USDC 铸币厂和 100 万美元的 USDC。账户内容变为:
IdlAccount,在我们更改数据以解码为 100 万美元的 USDC 存款后
接下来,攻击者会挖掘一个以 0x2c000000 结尾的虚荣密钥(因为它是小端字节序)。我们假设挖掘的虚荣密钥是十六进制 0xBBBBBB...BBBB2c000000。在我们的最后一步中,我们将创建一个虚假的时间戳( 0x11223344),并通过更改 32 字节的缓冲区权限来写入虚荣密钥的前 28 个字节。我们最终得到以下账户,该账户既是 IdlAccount
(除了鉴别器),又是我们虚荣地址的 100 万美元 USDC 的有效存款凭证。
在最后一步之后,我们将 IdlAccount 权限设置为时间戳和虚荣密钥的组合,这将完成角色扮演
攻击者现在可以使用虚假的 DepositReceipt
来利用我们假设容易受到类型混淆攻击的函数。
所有这些都表明,IDL 缓冲区为攻击者提供了一个非常强大的工具,可以利用启用了 IDL 上传的 Anchor 合约中的任何类型混淆漏洞,因为攻击者可以将几乎任意长度的几乎任意数据写入此账户。
再次,我想强调的是,如果没有完全独立的类型混淆漏洞,此 IDL 函数并不危险。
为了防止此类攻击,唯一的好方法是完全禁用 anchor 链上 IDL 功能。但是,如果你确信你的合约中没有类型混淆漏洞,因为你的每一条指令都正确地强制执行所有账户类型,那么确保你在其他人之前声明主 IDL 账户就足够了。
如果你受到恶意 IDL 攻击的影响,或者其他人声明了你的 IDL 账户,你唯一的补救措施是通过新的自定义指令执行程序升级,将 IDL 权限转移到你的账户。
总的来说,我们认为这是 Anchor 的一个设计问题。我们已与他们的团队联系,并建议默认情况下删除这些指令,理想情况下,将 IdlCreateAccount
指令限制为程序权限,或编译期间硬编码的另一个帐户。Anchor 团队告诉我们,他们已经意识到该问题,并且修复程序将在 v0.31.0 版本中发布,该版本即将发布。
- 原文链接: accretionxyz.substack.co...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!