Valibot 正则表达式拒绝服务漏洞深度剖析

本文详细分析了 Valibot 库中因 `EMOJI_REGEX` 存在缺陷而导致的 ReDoS 高危漏洞。攻击者可通过构造少于100个字符的恶意字符串,使正则表达式引擎陷入指数级回溯,消耗大量CPU时间,导致应用程序拒绝服务。

Valibot EMOJI_REGEX 中存在正则表达式拒绝服务漏洞

漏洞详情

该漏洞存在于 Valibot 库用于表情符号验证的 EMOJI_REGEX 中。一个简短(例如少于100字符)的恶意构造字符串,可以使正则表达式引擎消耗过量的CPU时间(数分钟),从而导致应用程序发生拒绝服务。

技术原理

此 ReDoS 漏洞源于 EMOJI_REGEX 中的“灾难性回溯”。这是由于正则表达式模式中重叠的字符类导致的歧义性。

具体来说,\p{Emoji_Presentation} 类与同一选择分支中使用的更具体的类存在重叠,例如用于国旗的区域指示符号 [\u{1F1E6}-\u{1F1FF}]\p{Emoji_Modifier_Base}

当正则表达式引擎尝试匹配一个几乎匹配但最终失败的字符串时(如概念验证中的例子),这种歧义性会迫使引擎探索指数级数量的可能路径。匹配时间随着构造输入字符串的长度呈指数增长,而非线性增长。

概念验证

以下代码演示了该漏洞。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import * as v from 'valibot';

const schema = v.object({
  x: v.pipe(v.string(), v.emoji()),
});

const attackString = '\u{1F1E6}'.repeat(49) + '0';

console.log(`输入长度: ${attackString.length}`);
console.log('开始解析... (这将花费很长时间)');

// 在我的机器上,长度为 99 的字符串大约需要 2 分钟。
console.time();
try {
  v.parse(schema, {x: attackString });
} catch (e) {}
console.timeEnd();

影响范围

任何在用户可控输入上使用 Valibot 表情符号验证的项目都容易受到拒绝服务攻击。

攻击者可以通过向任何使用此验证的端点提交一个短字符串来阻塞服务器资源(例如,Web 服务器的事件循环)。这尤其危险,因为攻击字符串足够短,可以绕过典型的输入长度限制(例如 maxLength(100))。

受影响的版本

  • 受影响版本:>= 0.31.0, < 1.2.0
  • 已修复版本:1.2.0

推荐的修复方案

根本原因是重叠的字符类。可以通过使各选项互斥来解决,通常使用否定前瞻 (?!...) 从更通用的类中减去特定的类。

以下是应用此原则的修改后的 EMOJI_REGEX

1
2
3
export const EMOJI_REGEX: RegExp =
  // eslint-disable-next-line redos-detector/no-unsafe-regex, regexp/no-dupe-disjunctions -- false positives
  /^(?:[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|(?![\p{Emoji_Modifier_Base}\u{1F1E6}-\u{1F1FF}])\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|(?![\p{Emoji_Modifier_Base}\u{1F1E6}-\u{1F1FF}])\p{Emoji_Presentation}))*)+$/u;

参考信息

安全指标

  • 严重等级: 高
  • CVSS 3.1 分数: 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
  • CWE: CWE-1333 - 低效的正则表达式复杂性
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计