本文提供了一种系统的方法,对区块链节点实现进行彻底的安全审查,以Paradigm的Rust Ethereum执行客户端Reth为例。主要分为环境搭建、预审准备(理解架构、威胁建模)、自动分析、手动代码审查策略(优先级框架、自下而上和自上而下的方法)、动态分析与测试、文档和报告编写等阶段,旨在帮助安全工程师识别关键漏洞,管理大规模分布式系统的复杂性。
审查核心区块链节点的安全性是安全工程师可以承担的最具挑战性的任务之一。与传统的 Web 应用程序或智能合约不同,区块链节点位于密码学、分布式系统和网络协议的交叉点——每个都有其独特的攻击向量和故障模式。
如果你曾经看过区块链节点代码库并感到不知所措,那么你并不孤单。我也经历过,盯着成千上万行的 Rust 代码,不知道从哪里开始。
本指南提供了一种系统的方法,用于对区块链节点实现进行彻底的安全审查,以 Reth(Paradigm 的 Rust Ethereum 执行客户端)作为我们的主要示例。无论你是区块链安全的新手还是希望规范你的审查流程,此方法都将帮助你识别关键漏洞,同时管理大规模分布式系统的复杂性。
核心区块链节点与其他软件系统根本不同。它们必须:
这种复杂性意味着传统的安全审查方法通常会失效。一个系统化的方法至关重要。
在深入代码审查之前,设置高效的开发环境对于有效地导航诸如 Reth 这样的大型代码库至关重要。合适的工具可以区分在成千上万行代码中挣扎和有条不紊地移动它们。
对于 Rust 区块链节点审查,你有几个可靠的选择:
Visual Studio Code(推荐给大多数用户)
RustRover (JetBrains)
Neovim/Vim(适用于终端爱好者)
对于 VS Code:
## 安装以下扩展:
code --install-extension rust-lang.rust-analyzer
code --install-extension vadimcn.vscode-lldb
code --install-extension tamasfe.even-better-toml
其他有用的扩展:
使用这些导航技术可以有效地浏览 Reth 的海量代码库:
Rust 专用导航:
大型代码库导航的专业技巧:
💡 利用大纲视图 - 显示当前文件中所有函数、结构体和模块
💡 拆分编辑器 - 在跟踪执行路径时,并排保持多个文件打开
通过设置此环境,你将能够有效地导航 Reth 的代码库,在相关组件之间快速跳转,并在进行安全审查时保持上下文。对适当工具的投资在整个审查过程中都会获得回报。
在深入研究代码之前,花时间了解系统架构。这个准备阶段至关重要,并且经常被新的审查人员低估。
现在我将这部分列为 1-3 天,但说实话,要完全理解整个 Ethereum 执行层架构,这是一个不现实的时间量(1-3 周都不够)。我们在这里的目标是对正在发生的事情有一个像样的高级理解。如果这是你的第一次审查,并且你没有时间压力,我建议将此时间增加到至少 1 周,甚至更多(建立你的领域知识)。现在,如果你有时间压力,请提前开始理论学习,并在正式启动之前开始。
对于 Reth,你的目标应该是基本了解以下每个组件的功能以及它们如何相互连接:
组件 | 目的 | 主要连接 |
---|---|---|
P2P 网络 | 用于传输交易和区块的节点间通信,以及用于节点同步的历史数据 | • 内存池(新交易)<br>• 数据库(区块)<br>• 状态转换(同步) |
RPC 层 | 面向用户的 HTTP/WebSocket 端点,dApp 连接到此端点以提交交易和查询数据 | • 状态转换(gas 估算、调用)<br>• 数据库(数据获取) |
Engine API | 共识-执行层桥梁,用于接收执行有效负载和分叉选择决策 | • 状态转换(区块执行)<br>• 内存池(有效负载构建)<br>• 数据库(存储已完成的区块) |
状态转换 | 执行交易和更新区块链状态的核心交易处理逻辑 | • EVM(智能合约执行)<br>• 数据库(状态读取/写入)<br>• 内存池(交易验证) |
数据库 | 用于区块、交易、账户状态和收据的持久存储 | • 状态转换(存储更新)<br>• P2P(区块持久化)<br>• RPC(提供数据)<br>• Engine API(已完成的区块) |
EVM | 使用 gas 管理和确定性执行来执行智能合约代码的虚拟机 | • 状态转换(交易执行)<br>• 数据库(读取合约代码/存储)<br>• Engine API(有效负载验证) |
内存池 | 管理挂起交易排序、费用和垃圾邮件预防的临时保持区域 | • P2P(接收交易)<br>• RPC(用户提交)<br>• Engine API(有效负载构建)<br>• 状态转换(验证) |
深入研究文档:
对于 Reth,首先从以下内容开始:
代码库映射:
💡 专业提示: 开发人员演练在这里非常有价值。这些会话允许你提出关于每个 crate 或模块的问题。即使你担心听起来很傻,也不要犹豫提出基本问题,最好尽早澄清基础知识,以加快你的理解并加快审查速度。
代码库映射的目的是连接你从上一节“了解架构”中学到的知识,并将其连接到源代码。
在执行此操作之后,希望我们不会像刚开始时那样迷茫。你将能够查看代码并思考“哦,这就是数据的存储位置”,“啊,网络文件夹包含用于管理我们的对等节点并通过 gossip 接收事物的代码”,以及“共识 crate 验证区块并准备它们以供执行”。
为此,我们需要查看代码,因此克隆存储库,打开代码并熟悉其结构。Reth 的模块化设计使其特别有用:
git clone https://github.com/paradigmxyz/reth
cd reth
code .
需要了解的关键 Reth crate:
reth-primitives
- 核心数据结构和类型reth-consensus
- 区块验证和共识逻辑reth-network
- P2P 网络和协议实现reth-rpc
- JSON-RPC API 实现reth-db
- 数据库抽象和存储层生成依赖关系图以映射模块之间的连接方式。下面的命令为你提供了一个很好的两层视图:首先是主要的 reth
依赖项,然后是这些依赖项所依赖的内容。密切关注任何拉入大量其他依赖项的第二层 crate,当我们采用自下而上的审查策略时,这些 crate 需要更多背景信息。
cargo tree --depth 2
操作理解:
设置本地测试环境,看看代码是否真的可以运行和工作。
## 运行 Reth 的测试套件
cargo test
## 启动本地开发节点
cargo run --bin reth node --dev
💡 专业提示: 如果构建失败或测试不稳定,抱歉,但你可能要进行一次艰难的审查。这通常表明你在安全审查期间会遇到更深层次的问题。
可以把它想象成暂时戴上了攻击者的帽子。我们想要了解两个关键的事情:坏人可以利用的入口点在哪里(攻击面),以及哪些类型的错误经常困扰区块链节点?这种前期威胁建模将指导我们的手动审查,并帮助我们将注意力集中在从安全角度来看真正重要的领域。
💡 专业提示: 通过询问开发人员他们最关心的攻击是什么,可以快速加速此步骤。同样,LLM 也非常适合这种高级别的头脑风暴。
攻击面分析:
好的,对于这个例子,我们将直接进入 Reth 架构。如果你觉得自己不胜任,请考虑自己尝试之前的步骤,以建立你对 Ethereum 和 Reth 的理解,或者询问你友好的邻里 LLM。或者,如果你不想进行深入的编码,请旨在了解我们为什么要执行这些步骤,而不会迷失在细节中。
网络层
reth-network/src/protocol
中的 P2P 消息处理 - 如果我们的对等节点向我们发送错误消息会怎样?共识层
reth-consensus
中的区块验证逻辑 - 如果我们收到一个错误的执行有效负载/区块会怎样?状态层
API 层
reth-rpc
中的 JSON-RPC 端点 - 我们能否用昂贵的 API 调用使节点过载?特定于区块链的攻击向量:
持续的威胁建模
虽然最初的威胁建模会话奠定了基础,但最有效的方法是在整个审查过程中进行持续的威胁建模。你的初始威胁模型必然受到你对系统的表面理解的限制,但是当你花费数天时间审查代码并理解组件交互时,你会发现从架构图中不明显的隐藏攻击面,以及你最初认为独立的组件之间微妙的交互错误。当你首次作为核心节点审查人员开始时,这非常真实。
实际方法很简单:每隔几天安排简短的 30 分钟的会话来重新审视你的威胁模型,我个人喜欢在每次完成一个 crate 或模块时这样做。然后询问自己发现了哪些新组件,关于系统行为的哪些假设被证明是错误的,以及在理解代码流程后出现了哪些新的攻击路径。在完成对每个主要组件的审查后,暂停一下,思考一下攻击者如何专门针对此组件,因为你现在了解了它的实现,并在你了解更多信息时查找跨组件漏洞。
这种持续的改进会将你的威胁模型从通用清单转变为有针对性的指南,该指南可以告诉你在哪里最有效地花费剩余的审查时间。通常,最关键的漏洞是从这种迭代过程中出现的,而不是从最初的头脑风暴会话中出现的,因为你不断发展的理解会揭示以你一开始无法看到的方式连接多个组件的攻击链。
懒一点!开玩笑的,有效率一点。如果工具有效率地为你完成工作,减少你的工作量;那就太好了 :) 虽然手动审查是安全评估的核心,但自动化工具提供了宝贵的覆盖范围,可以节省你的时间并找到简单的错误:
## Rust 专用安全分析
cargo clippy --all-targets --all-features -- -W clippy::all
cargo audit
cargo deny check
## 其他以安全为中心的工具
cargo install cargo-geiger # 不安全代码检测
cargo geiger
将自动化分析重点放在:
大型语言模型 (LLM) 可以成为你安全审查中有价值的盟友,尤其是在初始代码理解和模式检测方面。但是,它们是加速你审查的工具,而不是取代你的专业知识。
LLM 用例示例:
## 使用 LLM 进行代码解释和文档编制
## GitHub Copilot、Claude 或 GPT-4 的示例提示:
"函数 execute_block() 的作用是什么?"
[粘贴 execute_block() 函数]
"作为一名安全工程师,在处理 HTTP 端点时,我应该考虑哪些攻击向量?"
[粘贴 HTTP 端点文件]
“审查此交易验证逻辑是否存在我可能遗漏的边缘情况:”
[粘贴验证函数]
“我发现了这个错误,还有更多这样的情况吗?”
[粘贴错误的代码片段]
代码模式分析:
文档生成:
LLM 限制(务必记住):
最佳实践:
请记住:LLM 是研究助理,而不是安全专家。批判性思维、上下文理解和最终漏洞评估必须来自你。
手动审查通常会占用你 80% 的时间,并提供最深入的安全见解。这是我们希望效率最高的部分,但它也是最令人畏惧和最困难的部分,这使得你最有可能拖延和隐藏。成功进行手动审查的关键是系统地确定优先级。
1. 共识关键代码(最高优先级)
reth-consensus
中的区块验证2. 网络安全(高优先级)
reth-network
中的消息串行化/解串行化3. 状态管理(高优先级)
reth-trie
中的 Merkle 树实现4. API 和接口安全(中等优先级)
5. 配置和启动(中等优先级)
6. 资源管理(中等优先级)
7. 可观察性和监控(低优先级)
8. 开发和调试功能(低优先级)
现在我们已经确定了从哪里开始以及首先要查看哪些组件的优先级。接下来的问题是如何查看此组件?你可以应用两种通用的方法。
自下而上的方法(推荐给初学者)
从小处着手,系统地构建。从最容易理解的代码开始:基本数据结构、串行化函数和实用程序助手。选择不依赖于代码其他部分的 crate(cargo tree
会有所帮助)。使用这些作为理解整个系统的垫脚石。下面的进展显示了组件之间的大跳跃,但实际上,你会在每个步骤之间检查更多的中间层:
reth-primitives
中基本数据结构(区块、交易、标头)reth-evm
中交易执行和状态转换reth-consensus
中区块验证逻辑reth-engine-api
中共识客户端通信自上而下的方法(适用于经验丰富的审查人员)
自上而下的方法颠倒了自下而上的方法。你不是从基本组件开始,而是从用户或其他系统与你的节点交互的外部入口点开始,然后像通过代码调用图进行深度优先搜索一样跟踪执行路径。这就像侦探在案件中寻找线索一样,你从最初的犯罪现场(API 调用)开始,并沿着每个线索深入挖掘,在返回去寻找下一个线索之前,调查该链中的每个证人和证据。这种方法的主要优点是你可以立即分析实际的攻击场景,走与真实攻击者为破坏系统而利用的完全相同的代码路径。
对于 Reth 而言,Engine API 是一个理想的起点,因为它是共识层客户端 (Lighthouse) 与执行层 (Reth) 通信的地方,将关键数据(如新区块和分叉选择决策)发送到系统中。此接口代表了一个自然的入口点,恶意或格式错误的数据可能会进入 Reth,使其非常适合跟踪潜在攻击如何在代码库中传播。以下进展以线性列表的形式呈现是为了简化起见,但请记住,自上而下的分析实际上类似于树,每个组件都分支为多个子组件和依赖项。你会发现自己深入研究一个分支,然后回溯以探索另一个分支,然后再次跳到上一个分支,拥抱混乱。
reth-engine-api
开始,或者更具体地说,从 engine_newPayloadV3
端点开始,该端点处理新的执行有效负载并处理区块reth-consensus
reth-evm
执行reth-primitives
,因为它们是所有其他组件使用的基本构建块。方面 | 自下而上的方法 | 自上而下的方法 |
---|---|---|
复杂性管理 | ✅ 可管理,具有更小、更集中的模块 | ❌ 在组件之间跳转可能会让人不知所措 |
理解深度 | ✅ 深入了解基础组件 | ❌ 可能会错过实用程序函数中的细微漏洞 |
覆盖范围 | ✅ 系统地覆盖所有核心组件 | ❌ 可能会跳过未使用的代码路径(并不总是坏事) |
边缘情况 | ✅ 非常适合查找实用程序函数边缘情况 | ❌ 可能会错过基础边缘情况 |
攻击现实主义 | ❌ 最初很难评估实际的攻击场景 | ✅ 与攻击者方法自然对齐 |
时间效率 | ❌ 可能会在未使用的或影响较小的代码路径上花费时间 | ✅ 有效地专注于可访问的代码路径 |
业务逻辑 | ❌ 业务逻辑漏洞出现较晚 | ✅ 快速了解外部攻击面 |
所需的经验 | ✅ 适合初学者 | ❌ 需要大量的经验才能有效地进行导航 |
其他方法
正如你在上面的图表中看到的那样,每种方法都有优点和缺点。刚开始时,我采用了自下而上的方法,发现它非常适合学习和获得系统的代码覆盖率,然后我最终迁移到自上而下的方法。经过多年潜心研究这些大型代码库来磨练我的流程,我现在正在进行一种自上而下的变体,该变体还结合了以下一些技术。
main()
函数 - 在设置中有很多样板代码,并且配置问题通常严重程度较低,尽管它有助于构建对系统的理解以及所有组件的连接方式。如果时间允许,请使用动态测试来补充静态分析:
模糊测试关键组件:
在将花费的时间与发现的漏洞进行比较时,模糊测试通常会提供极好的投资回报。对于像 Reth 这样的区块链节点,你有几种模糊测试方法可供选择:
差异模糊测试(推荐用于 Reth)
我们采用了类似于 Beacon Fuzz 的差异模糊测试,它对于执行客户端特别有效,因为存在多个独立的实现(Reth、Geth、Erigon 等),对于相同的输入应该产生相同的结果。这种方法可以捕获可能不会触发明显崩溃的细微共识错误。
单客户端模糊测试
单客户端模糊测试通常会搜索 DoS 类型的错误,例如 panic、内存耗尽和执行缓慢,但它也可以发现一些小的错误,例如算术溢出和下溢。你还可以对 Reth 本身中的各个组件进行模糊测试:
入门
Rust Fuzz Book 提供了对模糊测试 Rust 代码库的出色介绍。从简单的串行化函数开始,然后再转移到更复杂的状态转换逻辑。
模糊测试对于查找解析逻辑和状态转换中的边缘情况特别有价值,而手动审查可能会遗漏这些情况。
边缘情况测试:
开发测试基础设施需要花费相当多的时间,但是,它可以获得回报。通常,当开发团队已经提供了良好的基础设施设置时,尝试这样做是件好事。
集成测试:
通常需要花费大量时间来设置和执行错误发现。将此工具保留在你有额外时间的情况下,或者更好地建议开发团队在自己的时间内执行这些测试。
错误发现只是安全审查中的一半工作——严重性分类才是真正专业知识的体现。准确的风险评估需要了解两个关键因素:攻击可达性(攻击者是否真的可以触发此代码路径?),以及潜在影响。这种细致的分析需要广泛的协议知识,并且是一个复杂的主题,值得拥有自己的专用指南。现在,以下是发现通常按严重程度级别分解的基础示例。
严重:共识失败、网络中断、资金损失
高:DoS 攻击、显着性能下降
中:信息泄露、轻微的 DoS
低:代码质量问题、最佳实践违规
使用以下内容记录发现:
编写报告所需的时间比大多数审查人员预期的要长得多,通常占你总审查时间的 +20%。不要将其作为最后的大量任务;在发现问题时记录发现,以保持上下文和详细信息。
主要原则:
为你的受众编写:你的报告是更广阔的世界对你的工作的看法。开发人员需要理解和修复这些问题,而用户需要评估此协议是否可行。
独立的解释:每个发现都应该是可以理解的,而无需打开 IDE 或阅读源代码。包括相关的代码片段,解释易受攻击的逻辑,并清楚地描述攻击路径。
💡 专业提示:
请记住:一份写得好的报告可以扩大你的安全工作的影响,而一份写得不好的报告甚至会使关键发现无效。
静态分析:
动态分析:
协议测试:
核心区块链节点的安全审查需要一种有条不紊的方法,该方法在全面覆盖和实际时间限制之间取得平衡。 关键原则是:
请记住,区块链节点安全性是一个不断发展的领域。 及时了解最新的攻击手段,参与安全社区,并根据新的学习不断改进你的方法。
像 Reth 这样的系统的复杂性可能会让人不知所措,但是通过结构化的方法和坚持不懈,你可以识别出关键的漏洞。 你作为安全工程师在这个领域的工作直接有助于数百万用户赖以生存的去中心化基础设施的安全性和稳定性。 从较小的组件开始,系统地建立你的理解,并且不要犹豫深入研究最重要的领域。
- 原文链接: blog.sigmaprime.io/core-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!