采用内存安全语言有助于 RPC 节点避免许多基于内存破坏漏洞的攻击,但仍需要经过精心设计和审查。前情提要:IOSG:由加密社群驱动的漏洞赏金、悬赏竞赛可行吗? 背景补充:深度分析 Sui 公链最新漏洞仓鼠滚轮为何被评为严重级?
本文目录
区块链 RPC 节点作用 审计角度:传统 RPC 伺服器 VS 区块链 RPC 伺服器 为何新区块链会采用记忆体安全的 RPC Layer 1 区块链中记忆体安全 RPC 威胁 显式的 Rust Panic 一种直接终止 RPC 服务的方法 隐式 Rust Panic 一种容易被忽视的终止 RPC 服务的方法 对开发人员的建议 总结CertiK 的 Skyfall 团队最近在 Aptos、StarCoin 和 Sui 等多个区块链中发现了基于 Rust 的 RPC 节点的多个漏洞。由于 RPC 节点是连线 dApp 和底层区块链的关键基础设施元件,其稳健性对于无缝操作至关重要。区块链设计者都知道稳定 RPC 服务的重要性,因此他们采用 Rust 等记忆体安全语言来规避可能破坏 RPC 节点的常见漏洞。
采用记忆体安全语言如 Rust有助于 RPC 节点避免许多基于记忆体破坏漏洞的攻击。然而,通过最近的审计,我们发现即使是记忆体安全的 Rust 实现,如果没有经过精心设计和审查,也很容易受到某些安全威胁的影响,从而破坏 RPC 服务的可用性。
本文我们将通过实际案例介绍我们对一系列漏洞的发现。
区块链的远端过程呼叫RPC服务是 Layer 1 区块链的核心基础设施元件。它为使用者提供重要的 API 前端,并作为通向后端区块链网路的闸道器。然而,区块链 RPC 服务与传统的 RPC 服务不同,它方便使用者互动无需身份验证。服务的持续可用性至关重要,任何服务中断都会严重影响底层区块链的可用性。
审计角度:传统 RPC 伺服器 VS 区块链 RPC 伺服器对传统 RPC 伺服器的审计主要集中在输入验证、授权 / 认证、跨站请求伪造 / 伺服器端请求伪造CSRF/SSRF、注入漏洞如 SQL 注入、命令注入和资讯泄露等方面进检查。
然而,区块链 RPC 伺服器的情况有所不同。只要交易是签名的,就不需要在 RPC 层对发起请求的客户端进行身份验证。作为区块链的前端,RPC 服务的一个主要目标是保证其可用性。如果它失效,使用者就无法与区块链互动,从而阻碍查询链上资料、提交交易或释出合约功能。
因此,区块链 RPC 伺服器最脆弱的方面是 可用性。如果伺服器当机,使用者就失去了与区块链互动的能力。更严重的是,一些攻击会在链上扩散,影响大量节点,甚至导致整个网路瘫痪。
为何新区块链会采用记忆体安全的 RPC一些着名的 Layer 1 区块链,如 Aptos 和 Sui,使用记忆体安全程式语言 Rust 实现其 RPC 服务。得益于其强大的安全性和编译时严格的检查,Rust 几乎可以使程式免受记忆体破坏漏洞的影响,如堆叠溢位、和空指标解引用和释放后重引用等漏洞。
为了进一步确保程式码库的安全,开发人员需严格遵循最佳实践,例如不引入不安全程式码。在原始码中使用 #![forbid (unsafecode)] 确保阻拦过滤不安全的程式码。
区块链开发者执行 Rust 程式设计实践的例子
为了防止整数溢位,开发人员通常使用 checkedadd、checkedsub、saturatingadd、saturatingsub 等函式,而不是简单的加法和减法、。通过设定适当的超时、请求大小限制和请求项数限制来缓解资源耗尽。
尽管不存在传统意义上记忆体不安全的漏洞,但 RPC 节点会暴露在攻击者容易操纵的输入中。在记忆体安全 RPC 实现中,有几种情况会导致拒绝服务。例如,记忆体放大可能会耗尽服务的记忆体,而逻辑问题可能会引入无限回圈。此外,竞态条件也可能构成威胁,并发操作可能会出现意外的事件序列,从而使系统处于未定义的状态。此外,管理不当的依赖关系和第三方库可能会给系统带来未知漏洞。
在这篇文章中,我们的目的是让人们关注可以触发 Rust 执行时保护的更直接的方式,从而导致服务自行中止。
显式的 Rust Panic 一种直接终止 RPC 服务的方法开发人员可以有意或无意地引入显式 panic 程式码。这些程式码主要用于处理意外或异常情况。一些常见的例子包括:
assert!():当必须满足一个条件时使用该 macro。如果断言的条件失败,程式将 panic,表明程式码中存在严重错误。panic!():当程式遇到无法恢复的错误且无法继续执行时呼叫该函式。unreachable!():当一段程式码不应该被执行时使用该 macro。如果该 macro 被呼叫,则表示存在严重的逻辑错误。unimplemented!() 和 todo!():这些巨集是尚未实现功能的占位符。如果达到该值,程式将崩溃。unwrap ():该方法用于 Option 或 Result 型别,当遇到 Err 变数或 None 时会导致程式当机。漏洞一:触发 Move Verifier 中的 assert!
Aptos 区块链采用 Move 位元组码验证器,通过对位元组码的抽象解释进行引用安全分析。execute () 函式是 TransferFunctions trait 实现的一部分,模拟基本块中位元组码指令的执行。
函式 executeinner () 的任务是解释当前位元组码指令并相应地更新状态。如果我们已经执行到基本块中的最后一条指令,如 index == lastindex 所示,函式将呼叫 assert!(selfstackisempty ()) 以确保栈为空。此行为背后的意图是保证所有操作都是平衡的,这也意味着每次入栈都有相应的出栈。
在正常的执行流程中,栈在抽象解释过程中始终是平衡的。堆叠平衡检查器Stack Balance Checker保证了这一点,它在解释之前对位元组码进行了验证。然而,一旦我们将视角扩充套件到抽象直译器的范围,就会发现堆叠平衡假设并不总是有效的。
AbstractInterpreter 中 analyzefunction 漏洞的更新程式抽象直译器的核心是在基本块级别中模拟位元组码。在其最初的实现中,在 executeblock 过程中,遇到错误会提示分析过程记录错误,并继续执行控制流图中的下一个块。这可能会造成一种情况:执行块中的错误会导致堆叠不平衡。如果在这种情况下继续执行,就会在堆叠不为空的情况下进行 assert! 检查,从而引发 panic。
这就使得攻击者有机可趁。攻击者可通过在 executeblock () 中设计特定的位元组码来触发错误,随后 execute () 有可能在堆叠不为空的情况下执行 assert,从而导致 assert 检查失败。这将进一步导致 panic 并终止 RPC 服务,从而影响其可用性。
为防止出现这种情况,已实施的修复中,确保了在 executeblock 函式首次出现错误时会停止整个分析过程,进而避免了因错误导致堆叠不平衡而继续分析时可能发生的后续崩溃风险。这一修改消除了可能引起 panic 的情况,并有助于提高抽象直译器的健壮性和安全性。
漏洞二:触发 StarCoin 中的 panic!
Starcoin 区块链有自己的 Move 实现分叉。在这个 Move repo 中,Struct 型别的建构函式中存在一个 panic! 如果提供的 StructDefinition 拥有 Native 栏位资讯,就会显式触发这个 panic!。
规范化例程中初始化结构体的显式 panic这种潜在风险存在于重新发布模组的过程中。如果被发布的模组已经存在于资料储存中,则需要对现有模组和攻击者控制的输入模组进行模组规范化处理。在这个过程中,”normalizedModulenew” 函式会从攻击者控制的输入模组中构建模组结构,从而触发 “panic!”。
规范化例程的前提条件通过从客户端提交特制的有效载荷,可以触发该 panic。因此,恶意行为者可以破坏 RPC 服务的可用性。
结构初始化 panic 更新Starcoin 的更新引入了一个新的行为来处理 Native 情况。现在,它不会引起 panic,而是返回一个空的 ec。这减少了使用者提交资料引起 panic 的可能性。
隐式 Rust Panic 一种容易被忽视的终止 RPC 服务的方法显式 panic 在原始码中很容易识别,而隐式 panic 则更可能被开发人员忽略。隐式 panic 通常发生在使用标准或第三方库提供的 API 时。开发人员需要彻底阅读和理解 API 文件,否则他们的 Rust 程式可能会意外停止。
BTreeMap 中的隐式 panic让我们以 Rust STD 中的 BTreeMap 为例。BTreeMap 是一种常用的资料结构,它以排序的二叉树形式组织键值对。BTreeMap 提供了两种通过键值检索值的方法:get (ampself key ampQ) 和 index (ampself key ampQ)。
方法 get (ampself key ampQ) 使用键检索值并返回一个 Option。Option 可以是 Some (ampV),如果 key 存在,则返回值的引用,如果在 BTreeMap 中没有找到 key,则返回 None。
另一方面,index (ampself key ampQ) 直接返回键对应的值的引用。然而,它有一个很大的风险:如果键不存在于 BTreeMap 中,它会触发隐式 panic。如果处理不当,程式可能会意外崩溃,从而成为一个潜在漏洞。
事实上,index (ampself key ampQ) 方法是 stdopsIndex trait 的底层实现。该特质为不可变上下文中的索引操作即 container [index]提供了方便的语法糖。开发者可以直接使用 btreemap [key],呼叫 index (ampself key ampQ) 方法。然而,他们可能会忽略这样一个事实:如果找不到 key,这种用法可能会触发 panic,从而对程式的稳定性造成隐性威胁。
漏洞三:在 Sui RPC 中触发隐式 panic
binance平台Sui 模组释出例程允许使用者通过 RPC 提交模组有效载荷。在将请求转发给后端验证网路进行位元组码验证之前,RPC 处理程式使用 SuiCommandPublish 函式直接反汇编接收到的模组。
在这个反汇编过程中,提交模组中的 codeunit 部分被用来构建一个 VMControlFlowGraph。该构建过程包括建立基本块,这些块储存在一个名为 “‘blocks” 的 BTreeMap 中。该过程包括建立和操作该 Map,在某些条件下,隐式 panic 会在这里触发。
下面是一段简化的程式码:
建立 VMControlFlowGraph 时的隐式 panic在该程式码中,通过遍历程式码并为每个程式码单元建立一个新的基本块来建立一个新的 VMControlFlowGraph。基本块储存在一个名为 block 的 BTreeMap 中。
在对堆叠进行迭代的回圈中,使用 block [ampblock] 对块图进行索引,堆叠已经用 ENTRYBLOCKID 进行了初始化。这里的假设是,在 block 反射中至少存在一个 ENTRYBLOCKID。
然而,这一假设并不总是成立的。例如,如果提交的程式码是空的,那么在 建立基本块 过程之后,块反射 仍然是空的。当程式码稍后尝试使用 amp blocks [ampblock]successors 中的 for succ 遍历块反射时,如果未找到 key,可能会引起隐式 panic。这是因为 blocks [ampblock] 表示式本质上是对 index () 方法的呼叫,如前所述,如果键不存在于 BTreeMap 中,index () 方法将导致 panic。
拥有远端访问许可权的攻击者可以通过提交带有空 codeunit 栏位的畸形模组有效载荷来利用该函式的漏洞。这个简单的 RPC 请求会导致整个 JSONRPC 程式崩溃。如果攻击者以最小的代价持续传送此类畸形有效载荷,就会导致服务持续中断。在区块链网路中,这意味着网路可能无法确认新的交易,从而导致拒绝服务DoS情况。网路功能和使用者对系统的信任将受到严重影响。
Sui 的修复:从 RPC 释出例程中移除反汇编功能值得注意的是,Move Bytecode Verifier 中的 CodeUnitVerifier 负责确保 codeunit 部分绝不为空。然而,操作顺序使 RPC 处理程式暴露于潜在的漏洞中。这是因为验证过程是在 Validator 节点上进行的,而该节点是在 RPC 处理输入模组之后的一个阶段。
针对这一问题,Sui 通过移除模组释出 RPC 例程中的反汇编功能来解决该漏洞。这是防止 RPC 服务处理潜在危险、未经验证的位元组码的有效方法。
此外,值得注意的是,与物件查询相关的其他 RPC 方法也包含反汇编功能,但它们不容易受到使用空程式码单元的攻击。这是因为它们总是在查询和反汇编现有的已释出模组。已释出的模组必须已经过验证,因此,在构建 VMControlFlowGraph 时,非空程式码单元的假设始终成立。
在了解了显式和隐式 panic 对区块链中 RPC 服务稳定性的威胁后,开发人员必须掌握预防或降低这些风险的策略。这些策略可以降低服务意外中断的可能性,提高系统的弹性。因此 CertiK 的专家团队提出以下建议,并作为 Rust 程式设计的最佳实践为大家列出。
Rust Panic Abstraction 尽可能考虑使用 Rust 的 catchunwind 函式来捕获 panic,并将其转换为错误资讯。这可以防止整个程式崩溃,并允许开发人员以可控的方式处理错误。
谨慎使用 API:隐式 panic 通常是由于滥用标准或第三方库提供的 API 而发生的。因此,充分理解 API 并学会适当处理潜在错误至关重要。开发人员要始终假设 API 可能会失效,并为这种情况做好准备。
适当的错误处理:使用 Result 和 Option 型别进行错误处理,而非求助于 panic。前者提供了一种更可控的方式来处理错误和特殊情况。
新增文件和注释:确保程式码文件齐全,并在关键部分包括可能发生 panic 的部分添加注释。这将帮助其他开发人员了解潜在风险并有效处理。
基于 Rust 的 RPC 节点在 Aptos、StarCoin 和 Sui 等区块链系统中扮演着重要的角色。由于它们用于连线 DApp 和底层区块链,因此它们的可靠性对于区块链系统的平稳执行至关重要。尽管这些系统使用的是记忆体安全语言 Rust,但仍然存在设计不当的风险。CertiK 的研究团队通过现实世界中的例子探讨了这些风险,也足以证明了记忆体安全程式设计中需要谨慎和细致的设计。
相关报导技术开倒车?但是很有趣!详解以太坊铭文协议Ethscriptions
技术》BNB Chain 新推出的L2opBNB是什么?
Beosin:Move VM先前毁灭级漏洞,可让Sui、Aptos崩溃、甚至硬分叉
Tags CertiKRPC内存节点记忆体
背景补充:Curve创办人急了!疑场外04镁卖出CRV,已三度偿还FRAX债务 DeFi 巨头 Curve 创办人 Michael Egorov 日前利用 CRV 抵押的债务仓位,作为多个 DeFi 协议的贷款担保,其中在 Fraxlend 借出了超过 1700 万枚的 FRAX,而在今天下午,Eg...
美国科技巨头微软Microsoft收购游戏巨头动视暴雪一案,本周有机会迎来最终章节,目前已有 40 个国家通过批准,包含中国、欧盟,最后一关即是美国与英国,接下来这周微软与美国联邦贸易委员FTC的听证会将会是关键。前情提要:微软收购动视暴雪恐失败?美国联邦贸易委员会(FTC)启动反垄断调查背景补充:...