灾难性回溯的成因
当模式里存在多个能匹配重叠内容的不定长重复,而整体又很可能失败时,引擎要穷举的分配组合数会随输入长度呈指数增长。这就是灾难性回溯:少量额外字符就能让耗时从毫秒跳到分钟。
为什么会失败得那么慢
失败是关键条件。如果输入很容易匹配成功,引擎很早就返回;一旦输入让整条模式在最后一步失败,所有中间尝试都必须走完,代价才会全部显现。因此攻击者只需构造一个「差一点就能匹配」的长输入。
基本信息
| 触发条件 | 重叠重复、嵌套重复或高度重复的分支,加上整体匹配失败 |
|---|---|
| 耗时特征 | 输入长度每增加一个字符,耗时可能翻倍 |
| 典型表现 | 进程占用单个核心持续满载、接口超时、服务不可用 |
| 安全含义 | 属于拒绝服务类风险,输入来源不可控时尤其危险 |
| 识别手段 | 用逐步加长的输入测耗时,观察是否呈指数曲线 |
| 缓解方向 | 重构模式、限制输入长度、加匹配超时、改用无回溯引擎 |
典型使用场景
- 排查接口偶发超时且集中在特定输入上
- 对用户输入做正则校验前做安全检查,避免被构造输入拖垮
- 代码评审时识别人工写出的危险模式
- 为关键路径加匹配超时与长度上限作为兜底
- 把危险模式重构为等价的线性行为模式
危险模式与安全改法
危险模式与安全改法
危险: (a+)+$ 重叠的嵌套重复,长串 a 加非 a 结尾即可拖满 CPU
安全: a+$ 去掉外层重复,语义等价且无重叠选择点
危险: (a|a)*b 分支重叠,每个位置都有多条等价路径
安全: a*b 合并等价分支
危险: ([a-z]+)*@ 嵌套重复叠加在字符类上
安全: [a-z]+@ 同上,去掉多余外层
危险: .*.*= 两个点号重复相邻,选择点相乘
安全: [^=]*= 用精确字符类收窄范围
兜底: 输入长度上限 + 匹配超时 + 无回溯引擎(如不需要反向引用)危险特征速查
| 特征 | 风险 | 改造方向 |
|---|---|---|
| 括号内加号再加外层加号 | 极高 | 去掉外层重复 |
| 相邻的两个点号重复 | 高 | 改为精确字符类 |
| 分支内容高度重叠 | 高 | 合并或调整分支顺序 |
| 未加锚点的长模式 | 中到高 | 补锚点缩小尝试起点 |
| 重复结构内部含可选 | 中到高 | 明确必填与可选边界 |
涉及用户输入的正则都应按可被恶意构造来设计:加输入长度上限与匹配超时是必要的兜底手段,仅靠优化模式不足以覆盖所有攻击形态。
验证是否修复到位,必须用加长输入实测耗时曲线,而不是只看小样本通过。若耗时随长度线性增长,说明危险结构已被消除。