本文对Substrate平台上的审计过程进行深入探讨,分析了常见漏洞和提供的工具,以加固区块链项目的安全性。作者还对Acala平行链的流动质押模块进行了系统分析,指出了一些建议和注意事项,对开发人员在进行审计时具有重要参考价值。
作者:Alexey Naberezhniy - MixBytes 的安全研究员
Polkadot 已成为一个蓬勃发展的生态系统,吸引了越来越多的项目。凭借其长期活跃的开发记录,它在创新方面继续引领潮流。尽管取得了成功,但能够审计基于 Substrate 平台构建的项目的公司仍然稀缺。此外,pallets 的审计过程仍然缺乏标准化,且信息有限。
为了阐明这一重要主题,我们对基于 Substrate 平台构建的项目中的主要漏洞以及可用于自动化审计过程的工具进行了独立研究。作为我们发现的补充,我们对 Acala parachain 的流动质押模块进行了全面分析,未发现关键问题,但提供了几项有价值的建议。
Substrate pallets 中的典型漏洞
1. Method on_initialized
描述
此方法在 pallet 中调用每个新区块。这个方法不得加载(或依赖可能引起加载的变量),并且不得导致 panic。否则,将会导致 DoS(拒绝服务攻击)。
2. 返回结果未处理 / Runtime panic 条件
描述
方法的结果可能未被处理或可能导致 panic。
示例
Option 类使用 unwrap 方法安全地处理 None。若对象中存在 None,unwrap 会导致 panic。这对于区块链基础设施至关重要。
相反,应该使用 unwrap_or_default。
另外,try_mutate_exists 的结果被忽略也是常见情况。这不是错误,但如果方法返回 None,可能会导致副作用。
3. 糟糕的 extrinsics 权重
描述
区块链资源的使用必须受到限制,以确保网络的连续运行。Substrate 使用权重机制来控制 extrinsic 的操作时间(类似于以太坊的 gas)。如果权重设置不正确,则垃圾邮件交易可能导致节点的 DoS。
示例
最显著的例子是一个循环。
##[weight = 1_000_000]
##[transactional]
pub fn work_with_users(origin: OriginFor<T>, users: Vec<T::AccountId>) -> DispatchResult {
let _ = ensure_signed(origin)?;
for user in users {
Self::do_something(&user)?;
}
Ok(())
}
如上所示,work_with_users 方法的权重不依赖于方法执行的实际复杂性(循环运行的次数未被计算在内)。
4. 溢出
描述
在 Rust 中有几种方式来处理数字。
与其他编程语言一样,当涉及用户输入的操作时,checked_add 是更可取的选择。
5. 需使用 transactional
描述
[transactional] 必须用于每个在 pallet 中的 extrinsic,否则状态在回滚时不会被取消。
顺便提一下,在新版的 Substrate 中,“transactional”是默认行为。
示例
...
Self::set_balance(currency_id, who, amount); # 存储的设置器
Err("some revert")?;
Ok(())
...
没有 transactional 的话,回退后余额记录将不会被删除。
6. EVM 中的经典漏洞(逻辑错误):
描述
示例
Substrate 是一种完全不同的技术,但错误对于任何智能合约来说都是相当标准的 https://learnblockchain.cn/article/12435。
然而,Substrate 和 EVM 审计之间存在非常重要的差异:
这些特点缩短了黑客可以进行的操作名单,因此减少了审计人员在检查 Substrate 项目时必须研究的攻击向量数量。
7. XCM 消息
XCM 消息可能存在以下问题:
示例
https://learnblockchain.cn/article/12434
8. 与 pallets 的交互
通常,一个单一的 parachain 使用多个相互交互的 pallets,用户与这些 pallets 互动。开发者必须非常小心,并提供所有测试,以确保能防止某个 pallet 错误地访问另一个 pallet(未预测的状态重写)。由于 pallets 类似于 EVM 中的智能合约,因此从一个 pallet 安全地访问另一个 pallet 的存储是至关重要的。
示例
例如,开发者使用 pallet_sudo 进行热修复,这在与区块链合作时并不是安全的方法。
9. 不正确迁移 Parachain 至新版本
迁移时应考虑在 pallet 中新增字段的所有添加,并检查在迁移过程中到新版本的转换是否正确。
用于简化与 Rust 交互的工具不是很多(在分析的背景下)。
RustSec Advisory Database 是一个针对通过 crates.io 发布的 Rust crates 提出的安全建议的存储库,由 Rust Secure Code Working Group 维护。
静态分析工具的一个示例是 rust-analyzer。它是 Rust 语言的模块化编译器前端。它是更大的 rls-2.0 工作的一部分,旨在为 Rust 提供出色的 IDE 支持。
使用 LLDB 调试来检测代码错误的众所周知的方法。
编写测试并检查它们将帮助你确保系统中所有可用的脚本能够正常工作。在 pallets 之间的集成测试尤为重要。
该脚本可以利用当前实时链的状态来启动新的 Substrate 链。使用此工具,可以创建 Polkadot、Kusama 和其他 Substrate 链的分叉以用于开发目的。
try-runtime 命令行工具使你能够使用内存外部数据结构查询运行时存储的快照。通过使用内存存储,你可以针对指定的运行时状态编写测试,从而在进入生产之前对真实链状态进行测试。
对 Acala 流动质押模块的研究攻击向量(附加内容)
commit: 37560835d477ee934fbca094af723d7d98b5f1d8
1. 管理功能中输入参数的验证
以下方法对输入参数的有效性检查不足,这可能导致系统暂时的 DoS:
例如:
所有这些方法都是admin方法,因此对协议没有直接威胁,因为通常所有来自管理员的调用都经过多重参数检查,但我们仍然建议添加检查以提高系统安全性。
2. 关于 LDOT 余额的数学运算
用户可以燃烧比 pallet 中的代币数量更多的 LDOT 吗?
这里不可能,因为 actual_liquid_to_redeem 总是小于 Self::to_bond_pool()。
total_redeem_amount = sum(RedeemRequests.redeem_amount) => 在此之前,LDOT 已在合约上 100% 加载,因为没有其他方式调用 do_request_redeem。
3. DOT 与 LDOT 的兑换率
在 convert_liquid_to_staking 或 convert_staking_to_liquid 方法中,可以人为改变兑换比例吗?
不能,因为此比例不会剧烈变化。
let total_staking = Self::get_total_staking_currency();
let total_liquid = Self::get_total_liquid_currency();
方法中的变化:
其余的价格变化是成比例的:
process_redeem_requests(燃烧质押和流动性)
mint(增加质押和流动性)
谁是 MixBytes?
MixBytes 是一支专业的区块链审计与安全研究团队,专门为 EVM 兼容和基于 Substrate 的项目提供全面的智能合约审计和技术咨询服务。请关注我们的 X,以便随时了解最新的行业动态和见解。
- 原文链接: mixbytes.io/blog/audit-o...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!