
本文系统讲解正则表达式拒绝服务(redos)的成因、识别方法与防护策略,帮助开发者在开放 api 场景下安全使用用户提交的正则表达式,避免因指数级回溯导致的 cpu 耗尽风险。
本文系统讲解正则表达式拒绝服务(redos)的成因、识别方法与防护策略,帮助开发者在开放 api 场景下安全使用用户提交的正则表达式,避免因指数级回溯导致的 cpu 耗尽风险。
正则表达式是文本处理的利器,但其背后潜藏的安全隐患不容忽视——尤其是正则表达式拒绝服务攻击(Regular Expression Denial of Service, ReDoS)。当正则引擎遭遇特定构造的“病态模式”(如嵌套量词、模糊回溯路径)时,匹配时间可能从毫秒级飙升至分钟甚至小时级,且呈指数增长趋势(O(2ⁿ) 或更高)。您提供的示例代码正是典型 ReDoS 模式:
String regex = ".*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*b.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*x"; boolean match = "aaabccc".matches(regex); // 30 个 .* 后接 b + 30 个 .* + x → 回溯爆炸
该模式在 Java 中触发的是 NFA(非确定性有限自动机)引擎的最坏回溯行为:.* 允许贪婪匹配任意长度,而多个 .* 连续出现时,引擎需反复尝试不同分割方式以满足后续 b 和 x 的位置约束。输入字符串越长(或越接近“失败边界”),回溯分支数呈指数膨胀,最终导致线程阻塞、服务降级甚至宕机。
? 如何识别“邪恶正则”(Evil Regex)
并非所有复杂正则都危险,但以下结构组合高度可疑(需同时满足多项):
- ✅ 嵌套量词:(a+)+、(.*a)*、(.*){2,}、.*.*.*(尤其重复 ≥3 次)
- ✅ 重叠可选路径:(a|aa)+、(x*y*)+、[a-z]+[a-z]*
- ✅ 后缀锚定失败触发深度回溯:^.*[a-z]+.*b$ 匹配 "aaaaaaaaa"(无 b)时,引擎需穷举所有 .* 分割点
- ✅ 未固化/未原子化的分组:(a+)+ 可优化为 (?>a+)+(原子组)或 a+(简化逻辑)
⚠️ 注意:Java 默认使用 Traditional NFA 引擎(支持捕获、懒惰量词、反向引用),天然存在 ReDoS 风险;而 DFA 引擎(如 egrep)虽免疫 ReDoS,但不支持捕获组和多数高级特性,不可直接替代。
?️ 实用防护方案
1. 静态分析 + 白名单预检
对用户提交的正则进行语法扫描,拒绝含高危模式的表达式:
public static boolean isPotentiallyDangerous(String regex) {
// 粗筛:连续 .* 出现 5 次以上
if (regex.replaceAll("\\.", ".").replaceAll("\\*", "*")
.replaceAll("\\+", "+").replaceAll("\\?", "?")
.split("\.\*", -1).length > 5) {
return true;
}
// 检测嵌套量词:如 (a+)+、(?:a+)+ 等
return Pattern.compile("(?:\([^)]*?\+\)|\[[^\]]*?\+\]|\w+\+)+").matcher(regex).find();
}2. 超时熔断机制
使用 Pattern.compile() + Matcher 手动控制匹配,并设置严格超时:
import java.util.concurrent.*;
import java.util.regex.*;
public static boolean safeMatch(String input, String regex, long timeoutMs) throws Exception {
Pattern pattern = Pattern.compile(regex);
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<Boolean> future = executor.submit(() -> pattern.matcher(input).find());
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new RuntimeException("Regex evaluation timed out — potential ReDoS", e);
} finally {
executor.shutdownNow();
}
}
// 调用示例:safeMatch("aaabccc", dangerousRegex, 100); // 100ms 超时3. 沙箱化执行(生产推荐)
将正则匹配移至独立进程或容器中,通过资源限制(CPU 时间片、内存)隔离风险:
- 使用 ProcessBuilder 启动轻量 JVM 子进程,配合 -XX:+UseSerialGC -Xmx16m 严控资源;
- 或采用 WebAssembly(如 WASI Regex)在沙箱内执行。
4. 替代方案:禁用用户自定义正则
对绝大多数业务场景(如搜索过滤、字段校验),优先使用结构化查询:
- 替代 .*keyword.* → 使用 String.contains("keyword") 或 indexOf()
- 替代复杂邮箱验证 → 调用 javax.mail.internet.AddressValidator 或 RFC 5322 兼容库
- 提供预置规则模板(如“手机号”“身份证号”),而非开放正则编辑框
✅ 总结
ReDoS 不是 Java 的 Bug,而是 NFA 引擎在特定模式下的固有行为。防御核心在于:不信任外部输入、不放任无限回溯、不牺牲可用性换取灵活性。在 API 设计阶段即应将正则视为“危险操作”,强制实施静态检查 + 动态超时 + 降级兜底三重保障。记住:一个被滥用的 .*,比千次 SQL 注入更易瘫痪服务——因为它的攻击载荷仅需 42 字符,却能让服务器沉默数分钟。
? 补充建议:定期使用 Regex Fuzzer 工具扫描存量正则;升级 JDK 至 17+,利用 Pattern.compile(regex, Pattern.CANON_EQ) 等新选项提升引擎鲁棒性(部分版本已优化回溯剪枝逻辑)。

















