← 中文资源

简体中文版本

正则表达式拒绝服务(ReDoS)

ReDoS 利用正则引擎的灾难性回溯:构造输入让匹配时间指数增长,单个输入就能把请求处理器挂在单个 CPU 核上。

阅读英文原文

概念定义

回溯引擎先试一条路径再回退重试。当多个带量词的组能匹配同一批字符——可选组上嵌套星号再要求尾字符——重试树的规模随每个输入字符翻倍。

工作机制

攻击者构造“几乎匹配”的输入最大化歧义:歧义前缀重复多次、却不给满足的后缀,每种歧义都翻倍路径。安全写法用原子分组与占有量词让引擎认账不回头,或用有界重复封顶重试;RE2、Rust regex 等线性引擎则以放弃部分特性换保证线性时间。

使用场景

审计一切可从请求数据触达的正则,包括继承自库与框架的模式。静态分析器能标记灾难性模式;模糊测试搜索慢输入;CI 里用病理样例做基准。

常见误区

“输入够大才危险”不成立——二三十个字符可能就够,复杂性在模式不在载荷。“测过一次就安全”也不成立:一次通过只走了一条回溯路径。编译只加速不改变指数语义;由用户片段拼装模式会让嵌套量词悄然复现。线性引擎缺反向引用与环视,部分规则移植不了;各引擎的超时机制不统一,只能当兜底不能当修复。

代码示例

export function escapeRegExp(input) {
  if (typeof input !== "string") throw new TypeError("input must be a string");
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

export function literalRegExp(input, flags = "") {
  if (typeof flags !== "string") throw new TypeError("flags must be a string");
  if (/[gy]/.test(flags)) {
    throw new RangeError("stateful flags g and y are not allowed");
  }
  return new RegExp(escapeRegExp(input), flags);
}
package main

import (
	"crypto/sha256"
	"crypto/subtle"
	"fmt"
)

func ConstantTimeEqual(a, b string) bool {
	left := sha256.Sum256([]byte(a))
	right := sha256.Sum256([]byte(b))
	return subtle.ConstantTimeCompare(left[:], right[:]) == 1
}

func main() {
	secret := "s3cr3t-token"
	fmt.Println(ConstantTimeEqual(secret, "s3cr3t-token"))
	fmt.Println(ConstantTimeEqual(secret, "s3cr3t-tokeX"))
	fmt.Println(ConstantTimeEqual(secret, "short"))
}