Regolith:从根本上免疫 ReDoS 攻击的服务器端正则表达式库
你是否曾担心过编写的正则表达式可能成为服务中的安全漏洞?是否听说过“正则表达式拒绝服务攻击”(ReDoS)却不知其具体含义?今天我们将深入探讨一个能够从根本上解决这一问题的开源工具——Regolith。
什么是 ReDoS 攻击?
正则表达式拒绝服务(ReDoS)攻击是一种特殊类型的拒绝服务攻击,它利用的是某些正则表达式引擎在处理特定模式时的设计缺陷。当恶意攻击者精心构造的输入与 vulnerable 的正则表达式模式相遇时,会导致引擎进入指数级增长的计算复杂度,从而消耗大量服务器资源,使服务变得不可用。
想象一下,一个简单的表单验证正则表达式,正常情况下处理用户输入只需几毫秒,但在遭受 ReDoS 攻击时,可能会使服务器卡在该计算上长达数小时!这种攻击之所以危险,是因为它不需要大量请求流量,只需要一个精心构造的请求就能达到攻击效果。
现有正则表达式引擎的问题
大多数编程语言(包括 JavaScript 和 TypeScript)内置的正则表达式引擎都存在这个问题。它们使用的是基于回溯的算法,在最坏情况下会出现指数级的时间复杂度增长。
研究表明,当输入规模达到 30 个字符时,JavaScript 的标准 RegExp 引擎处理某些恶意模式可能需要超过 10 秒的时间。相比之下,线性时间复杂度的引擎处理同样的输入只需要不到 1 毫秒。
Regolith 的解决方案
Regolith 是一个服务器端 TypeScript 和 JavaScript 库,它通过使用 Rust 编写的线性时间复杂度正则表达式引擎,从根本上免疫了 ReDoS 攻击。
技术原理
Regolith 的核心在于使用了 Rust 语言的高性能正则表达式库。Rust 的正则表达式库经过精心设计,故意排除了会使引擎容易受到 ReDoS 攻击的功能特性——主要是回溯引用(backreferences)和环视(look-around)操作。虽然这意味着某些高级功能不可用,但换来的却是执行时间的线性保证。
为什么选择 Rust?
Rust 语言以其内存安全性和高性能而闻名。Rust 的正则表达式库在社区中已经得到了广泛验证和信任,被用于许多对性能和安全要求极高的场景。Regolith 通过 napi-rs 创建 JavaScript 绑定,让 JavaScript 和 TypeScript 开发者也能享受到这一强大引擎的优势。
直接替换体验
Regolith 最令人称赞的特点之一是它试图成为 RegExp 的直接替代品,几乎不需要修改现有代码即可使用。这意味着你可以轻松地将现有项目中的正则表达式操作迁移到 Regolith,立即获得对 ReDoS 攻击的免疫力。
实际应用案例
现实中的 ReDoS 漏洞
2025 年发现的 CVE-2025-5889 漏洞影响了 brace-expansion 库,这是一个被 4250 万个 GitHub 项目使用的流行库。这个漏洞本身不是该项目的过错,而是语言层面允许这类问题存在的必然结果。
修复这样一个漏洞需要巨大的生态系统努力:需要向后兼容多个版本,每个使用该库的项目都需要更新、测试和部署。如果每个人都保持软件更新,这将意味着 4250 万个拉取请求,大约 4250 万分钟的构建时间,以及可能超过 4200 万工程师分钟的工作量。
而使用 Regolith 这样的免疫库,可以一次性解决所有这类潜在问题,为整个生态系统节省大量资源。
如何开始使用 Regolith
安装
安装 Regolith 非常简单,只需要运行以下命令:
npm i @regolithjs/regolith
基本使用
使用 Regolith 与使用标准 RegExp 非常相似:
import { Regolith } from '@regolithjs/regolith';
// 创建正则模式
const pattern = new Regolith("^\\d+$");
// 使用模式进行测试
pattern.test("12345"); // 返回 true
pattern.test("Hello"); // 返回 false
完整功能示例
Regolith 支持所有常见的正则表达式操作:
匹配示例
import { Regolith } from '@regolithjs/regolith';
const pattern = new Regolith('crab', 'g');
console.log(pattern.test('my crab ferris')); // 输出: true
查找所有匹配
const sentence = 'crab, snail, crab';
const crabPattern = new Regolith('crab', 'g');
console.log(crabPattern.match(sentence));
// 输出: ['crab', 'crab']
替换操作
const sentence = 'crab, snail, crab';
const crabPattern = new Regolith('crab', 'g');
console.log(crabPattern.replace(sentence, 'snake'));
// 输出: 'snake, snail, snake'
搜索操作
const sentence = 'crab, snail, crab';
const snailPattern = new Regolith('snail');
console.log(snailPattern.search(sentence));
// 输出: 6 (找到'snail'的位置索引)
分割操作
const splitPattern = new Regolith('[,\\|]');
console.log(splitPattern.split('apple,banana|orange'));
// 输出: ['apple', 'banana', 'orange']
Express.js 集成示例
以下是一个在 Express.js 应用中使用 Regolith 的完整示例:
import express from "express";
import { Regolith } from "@regolithjs/regolith";
const app = express();
const port = 3000;
// 创建 Regolith 正则模式
const intPattern = new Regolith("^\\d+$");
const floatPattern = new Regolith("^\\d*\\.\\d+$");
app.get("/check", (req, res) => {
const value = req.query.value;
if (!value) {
return res.status(400).send("请提供 value 查询参数");
}
// 使用 Regolith 模式进行测试
const isInt = intPattern.test(value);
const isFloat = floatPattern.test(value);
res.json({
value,
isInt,
isFloat,
});
});
app.listen(port, () => {
console.log(`示例应用监听端口 ${port}`);
});
这个简单的 API 端点可以检查输入值是整数还是浮点数,而且完全免疫 ReDoS 攻击。
开发与构建
如果你想参与 Regolith 的开发或者自己构建这个库,以下是需要的步骤:
前置要求
-
需要安装 yarn包管理器 -
需要安装 Rust 工具链(通过 rustup)
构建过程
# 克隆项目
git clone https://github.com/JakeRoggenbuck/regolith.git
# 安装依赖
yarn install
# 构建项目
yarn build
构建过程会编译 Rust 代码并生成必要的绑定文件。
测试
Regolith 包含完整的测试套件:
# 测试 TypeScript/JavaScript 库
yarn test
# 测试 Rust 绑定
cargo test
当前有 93 个测试用例确保库的稳定性和正确性。
平台兼容性
Regolith 已经过多种平台的测试,包括:
平台 | 状态 |
---|---|
Arm 64 Apple Darwin | 正常工作 |
Arm 64 Linux GNU | 正常工作 |
x86-64 Linux MUSL | 正常工作 |
i686 PC Windows MSVC | 正常工作 |
RISC-V 64 GC Linux GNU | 正常工作 |
当前限制与未来规划
浏览器兼容性
目前 Regolith 主要专注于服务器端 JavaScript 和 TypeScript 应用,因为服务器是 ReDoS 攻击的主要目标。在客户端使用(如 React 的 “use client”)还存在一些挑战,主要是本地库的链接问题。
团队正在积极解决这一问题,可能通过 WebAssembly 或自研正则表达式引擎实现。这一进展可通过issue #40跟踪。
功能权衡
由于选择了保证线性时间复杂度的设计,Regolith 不支持某些高级正则表达式特性:
-
回溯引用(backreferences) -
环视(look-around)操作
这一权衡在 Rust 社区已经被证明是值得的,大多数应用场景并不需要这些高级特性,而安全性和性能的收益是实实在在的。
常见问题解答
Regolith 与标准 RegExp 完全兼容吗?
Regolith 试图成为标准 RegExp 的直接替代品,但有两个重要区别:一是它不支持回溯引用和环视操作;二是它保证了线性时间复杂度,免疫 ReDoS 攻击。对于大多数使用场景,迁移到 Regolith 不需要修改代码。
为什么选择 Rust 而不是其他语言?
Rust 的正则表达式库已经经过广泛验证和优化,提供了卓越的性能和安全性。通过 napi-rs 创建绑定,可以让 JavaScript 生态享受到这些优势,而无需重新发明轮子。
Regolith 会影响性能吗?
在绝大多数情况下,Regolith 的性能与标准 RegExp 相当甚至更好。只有在处理某些复杂模式时,由于不支持一些高级特性,可能会有功能上的限制,但这换来的是安全性的极大提升。
如何报告 bug 或贡献代码?
如果发现 bug,可以通过电子邮件发送至 bug@jr0.org
或在 GitHub 上提交 issue。欢迎社区贡献代码和改进建议。
项目名称的由来
Regolith 这个名字来源于地质学,指的是行星表面由尘埃和岩石组成的顶层。作者在寻找以”reg”开头的单词时想到了这个术语, likely 是在学习恐龙相关课程时熟悉了这个词。
动机与背景
Regolith 的创作灵感来源于对为什么某些语言容易受到 ReDoS 攻击而其他语言不会的本科研究。作者发现一个问题:”为什么像 TypeScript、JavaScript 和 Python 这样的语言,最流行的正则表达式库不是线性时间引擎?”
JavaScript 生态中确实存在一些解决这一问题的库,如封装了 Google RE2 库的 re2js 和 node-re2,但它们要么API不同,要么采用率不高。绝大多数项目仍然使用默认的正则表达式引擎,这使得它们容易受到 ReDoS 攻击。
Regolith 的目标就是提供一个直接替代方案,让开发者无需担心 ReDoS 攻击,同时尽可能减少迁移成本。
总结
Regolith 代表了一种务实的安全方法:通过使用经过验证的技术(Rust 的正则表达式引擎)和简单的直接替代API,为 JavaScript 和 TypeScript 生态系统提供对 ReDoS 攻击的免疫力。
虽然它需要权衡一些高级功能,但对于绝大多数应用场景来说,这种权衡是值得的。随着网络安全威胁日益复杂,采用像 Regolith 这样从根本上设计安全的工具,将是构建 resilient 系统的关键一步。
无论你是维护一个被数百万项目使用的大型库,还是只是编写一个简单的表单验证,Regolith 都能帮助你避免潜在的服务中断和安全事件,让你能更专注于构建功能而非担心安全漏洞。