安全测试正则表达式的工作流
面向 JavaScript 正则表达式的编写—测试—重构循环:真实样本数据、灾难性回溯防护、锚定纪律、标志位语义,以及为什么在浏览器中测试能让模式保持私密。
循环:编写、测试、重构
可靠的正则从来不是一次写成的。从能匹配一个真实例子的最小模式开始,运行它,再对照下一个例子放宽或收紧。样本文本是规格,模式是实现。
开启自动运行后,测试器会在你暂停输入片刻时求值。这样可快速完成编辑—测试循环,同时避免每次击键都启动一次运行。
- 从样本开始对着具体样本写模式,而不是凭记忆:粘贴一行你打算解析的日志或文档的真实内容。
- 测试每次修改每次修改后都测试——正则没有编译警告,唯一的反馈就是它匹配了什么、漏掉了什么。
- 为读者重构通过之后为阅读而重构:命名捕获组与非捕获组让能工作的模式对下一位读者也能存活。
敬畏灾难性回溯
JavaScript 的正则引擎会回溯:匹配失败时,它会用不同的量词长度重试。(a+)+ 这类嵌套量词作用在险些匹配的字符串上,会让重试次数指数级爆炸——在良好输入上瞬间作答的模式,在恶意输入上可能跑好几年。攻击者称之为 ReDoS。
JavaScript 正则测试器在专用 Web Worker 中运行模式,预算为 650 毫秒;失控表达式会得到超时结果。若真实输入触发超时,应在生产环境所用引擎及输入上限下再次测试,不能直接断言生产事件循环一定卡住。
- 嵌套量词经典形态是量词组内含量词——(a+)+、(\w+)*——同一字符串的许多种划分都能满足内部模式。
- 歧义交替歧义交替也会引爆:(a|aa)+ 给引擎两种匹配每个额外字符的方式,搜索树随字符数翻倍。
- 结构性修复修复是结构性的:让内部匹配无歧义、用显式长度限制约束,并且务必测试险些匹配的输入,而不只是合法输入。
先锚定,再信任匹配
能找到子串的模式,不等于能校验值的模式。/\d+/ 会在 abc123xyz 内部匹配;/^\d+$/ 只接受纯数字。大多数校验缺陷是缺少锚点,而大多数提取缺陷是不该有的锚点。
- 开头与结尾^ 与 $ 把匹配钉在字符串的开头与结尾——开启多行标志时则钉在每一行,这会悄悄改变“整个输入”的含义。
- 词边界\b 词边界防止短模式匹配进更长的单词:\bcat\b 找的是那只动物,而不是 concatenate 里的 cat。
- 搜索还是校验一开始就决定搜索还是校验:提取模式通常不要锚点,校验模式几乎总是两个都要。
用真实样本数据测试
用三个整洁例子证明过的模式,会在生产环境的第四个真实输入上失败。构建反映输入分布的样本集:最长的合法值、空字符串、空白变体、Unicode 名字,以及那个几乎该匹配但必须不匹配的险些样本。
- 险些样本包含险些样本:邮箱模式应该用 a@b 和 a b@c.d 测试,而不只是格式良好的地址。
- Unicode数据包含 Unicode 时应加入相应样本:启用 u 后,. 按 Unicode 码点匹配,但 \w 并不是通用 Unicode 字母类。测试带重音的名字,合适时使用 \p{L} 等属性转义。
- 重跑整个集合把样本集放在模式旁边;模式一变,重跑整个集合,而不只是促成修改的那个用例。
标志位改变的是语言,不只是搜索
标志位是模式含义的一部分。g 决定你拿到第一个匹配还是全部,i 折叠大小写,m 把 ^ 和 $ 重新锚定到行,s 让点号跨行,u 把引擎切换为语法更严格的 Unicode 感知模式。
工作区按你选择的标志位求值,列出每个匹配的起止偏移、带各自位置的编号与命名捕获组,以及实时的替换预览——标志位的变化体现为结果上的具体差异,而不是理论。
- g 与 lastIndex没有 g,test() 与 match() 只看到第一个命中;有了 g,正则对象携带 lastIndex 状态,反复调用 test() 会交替返回结果——一个臭名昭著的生产缺陷。
- s 放宽点号s(dotAll)让 . 匹配换行,全局启用时可能悄悄放大旧模式的胃口。
- u 及其伙伴u 启用 Unicode 感知的转义与更严格的语法,但不会把 \w 或 \d 变成通用 Unicode 字母或数字类。测试器也支持 d(匹配索引)、y(粘性)和 v(Unicode 集合)。
让模式与样本留在本地
测试数据往往就是真实数据:客户邮箱、内部日志行、事故报告里的标识符。把它粘进托管测试器就是把它送进别人的基础设施。基于浏览器的测试器把整个会话留在标签页里——模式与样本文本由你自己设备的引擎求值,绝不传输。
本地运行时对上限很明确:模式最长 4,096 字符,样本文本最长 100,000 字符,替换串最长 20,000,单次运行最多报告 1,000 个匹配。这些限制面向开发与调试;批量日志分析属于本地脚本,而不是浏览器标签页。
只复制出你需要的东西——完成的模式与替换串——把样本数据留在它开始的地方:你的机器上。
在本地测试一个模式常见问题
什么是灾难性回溯,如何避免?
在浏览器里测试正则表达式是私密的吗?
全局(g)标志实际上改变了什么?
提取出的数据通常落进 JSON。
模式从原始文本中抽出值之后,用一套确定性的本地流程检视并 diff 得到的负载。