一条行为序列正则把风控接口线程池打满:ReDoS 的 3 个隐藏触发点
title: 一条行为序列正则把风控接口线程池打满ReDoS 的 3 个隐藏触发点topic: 正则表达式引擎原理与性能陷阱batch: 6round: 3我们风控系统要判断「用户是否完成了 点击→浏览→加购→购买 的转化路径」早期同学写了个正则直接匹配上报的行为序列字符串。平时跑得好好的直到有一天某渠道刷子把一个用户的行为串拼了 1800 个字符上报风控接口的平均 RT 从 12ms 飙到 8 秒线程池被打满正常请求全排队。这不是正则「写错了」是正则引擎的「回溯」机制在恶意/脏输入下指数爆炸——ReDoS正则拒绝服务。这篇文章用一个真实事故把回溯为什么慢、哪些写法会引爆、怎么止血讲透。事故嵌套量词 长输入回溯指数爆炸当时匹配行为序列的正则简化public class BehaviorMatcher { // 判断行为串里是否包含「点击浏览加购购买」的转化路径 private static final String PATTERN (点击|浏览|加购|购买); public boolean isConversion(String seq) { return seq.matches(PATTERN); // 整串匹配 } }逐行解释为什么这版在长输入下会炸- 第 3 行(点击|浏览|加购|购买)是个「嵌套量词」套在分组上分组内部又是多选。Java 的Pattern默认是 NFA 引擎匹配时遇到「可以走多条路」的分支会回溯——先贪心吃掉所有字符发现后面接不上再一步步吐回来试别的路。- 第 5 行seq.matches(PATTERN)做整串匹配。当输入是「点击点击点击…1800 个购买」这种接近匹配但又卡在末尾的结构时引擎要在「把前面的点击分给 的哪一次重复」上尝试海量组合回溯次数随输入长度近似指数增长。1800 字符的灾难输入回溯量到了几亿次单次匹配耗时从微秒级变成秒级。- 关键认知正常短输入几十字符回溯量小你永远测不出来ReDoS 是「输入越长越炸」平时绿、大促/被刷时崩这正是它最阴的地方。止血一用「占有量词」或「原子组」掐掉回溯public class BehaviorMatcher { // 占有量词 * 匹配后不吐回杜绝回溯 private static final String PATTERN (?:点击|浏览|加购|购买)*; private static final Pattern COMPILED Pattern.compile(PATTERN); public boolean isConversion(String seq) { return COMPILED.matcher(seq).find(); // 用预编译的 Pattern } }逐行解释- 第 3 行(?:...)*的*是「占有量词」possessive含义是「匹配能匹配的尽量多且绝不回退」。这样即使后面接不上引擎也不回头重试回溯被物理掐断灾难输入也只线性扫描一遍毫秒级返回。- 第 4 行Pattern.compile(PATTERN)把正则预编译成静态常量——这是另一个救命点见下一节。正则对象编译有成本每次matches内部会重编译。- 第 7 行COMPILED.matcher(seq).find()用预编译对象且find()比matches()强制整串更贴合「是否包含路径」的语义也避免因为整串锚定引入的额外回溯分支。第二个触发点每次请求都 Pattern.compile热点接口 CPU 偷偷涨我们另一个接口校验用户输入的「时间窗」格式当时这么写public class TimeWindowValidator { public boolean valid(String input) { // 每次调用都重新编译正则高频接口下编译开销被放大 return Pattern.compile(^\\d{2}:\\d{2}-\\d{2}:\\d{2}(,\\d{2}:\\d{2}-\\d{2}:\\d{2})*$) .matcher(input).matches(); } }逐行解释- 第 4 行Pattern.compile(...)在方法内部意味着每个请求都编译一次正则。正则编译不是免费的要解析语法、构建状态机单次几微秒到几十微秒这个接口 QPS 2 万光编译正则就吃掉一个 CPU 核火焰图上Pattern.compile赫然在榜。- 这虽然不是 ReDoS不会因为输入爆炸但是「隐性的 CPU 泄漏」平时不明显流量一高就暴露。我们压测时发现这个接口的 CPU 有 18% 耗在 compile 上改成静态预编译后直接降下来。- 修法和上一节一样把Pattern提成static final常量方法里只matcher(input)。正则对象线程安全可以放心共享。第三个触发点.*跨多行 贪婪把整段文本拖进来还有个坑我们栽在「日志字段提取」上想从一整段多行日志里抓traceIdxxx写了traceId.*结果public class LogExtractor { private static final Pattern P Pattern.compile(traceId.*); public String extract(String bigLog) { Matcher m P.matcher(bigLog); return m.find() ? m.group() : null; } }逐行解释- 第 2 行traceId.*的.默认不匹配换行符看似安全但在「单行超长日志」我们有的访问日志一行 20KB里.*会贪婪匹配到行尾再把后面所有traceId出现的位置都卷进来匹配范围远超预期既慢又抓错。- 更糟的是如果日志里有换行、你又加了Pattern.DOTALL模式.*直接把整段文本吞了匹配长度从「到行尾」变成「到文件尾」内存和耗时都爆。- 修法明确边界用traceId([\w-])只抓 token 本身.换成具体的字符类别用裸.*。「用具体字符类代替.」是写正则的保命习惯尤其在不确定输入长度的地方。哪些写法容易引爆回溯一张清单写法为什么危险安全替代(a)嵌套量词分组内外的互相回溯(a)*或原子组(?a)(a\|a)多选 嵌套量词去掉冗余多选或用占有量词.*/.贪婪且无边界吞太多、回溯多用具体字符类如[^,]*(a\|b)*c后接长串多选后跟必须匹配的字回溯试探(?[ab])*c原子组我的取舍写正则时默认假设「输入可能是恶意的长串」。凡是出现「量词套量词」「.*不带边界」「多选后跟必匹配字符」这三种结构立刻警觉优先改成占有量词/*或原子组(?...)。Java 没有re2那种线性时间引擎Go 的regexp是所以回溯风险要靠写法自己规避不能指望引擎兜底。怎么提前发现 ReDoS两条工程手段光靠人眼 review 不够我们后来加了两条1.压测用超长/畸形输入正常用例只测「短输入」ReDoS 永远测不出。我们在正则相关的接口压测里强制跑一组「10 倍长度 接近匹配但卡壳」的输入专门逼回溯。这条规则上线后两个潜在 ReDoS 在压测阶段就暴露了没上线。2.静态扫描「危险写法」CI 里加了一条正则 lint我们用的自写规则 开源的Semgrep规则凡是匹配到(...)嵌套量词、裸.*就报警让人工确认。误报有但比线上崩一次值。兜底手段给输入限长 用 region 限制扫描范围即便你改用了占有量词我仍建议加一道「输入长度闸」因为 ReDoS 不止回溯这一种——超长输入本身也会把任何正则拖慢线性但系数大。public class SafeMatcher { private static final int MAX_LEN 4096; // 业务上行为串不可能超过 4KB public boolean isConversion(String seq) { if (seq.length() MAX_LEN) return false; // 超长直接拒不进正则 Matcher m COMPILED.matcher(seq); m.region(0, Math.min(seq.length(), MAX_LEN)); // 即使前面漏了也限制扫描区间 return m.find(); } }逐行解释- 第 4 行seq.length() MAX_LEN是「长度闸」行为串业务上不会超过 4KB超过的直接当脏数据拒掉根本不进正则引擎把「超长输入拖慢」和「灾难回溯」两道雷一起挡了。- 第 6 行m.region(0, ...)显式限制匹配扫描的区间即使前面判断漏了引擎也只在前面 4KB 里扫避免整段大文本被拉进来。这是我们压测时加的双保险属于「纵深防御」占有量词治回溯长度闸治超长。- 我建议所有「用户输入进正则」的接口都加这道闸——成本低一行判断收益是把「不可信输入」和「正则引擎」之间加了一道硬墙。我们加完之后即便哪个正则写法漏了占有量词超长输入也进不来爆炸半径被锁死在 4KB 内。复盘真实数字行为序列正则灾难输入 1800 字符单次匹配从 12ms 飙到 8 秒风控接口线程池200 线程30 秒内打满正常请求排队超时率 6%。改成占有量词(*) 预编译同输入 1800 字符耗时回到 0.3ms线程池水位稳定在 15%。时间窗校验Pattern.compile移出方法体该接口 CPU 占用降 18 个百分点QPS 2 万下省出一个核。正则 lint 进 CI 后两个隐藏 ReDoS 在压测/扫描阶段拦截未上线。我的取舍正则不是不能写是别替它兜底的输入我不建议用正则在热路径上做「复杂结构匹配」——尤其是行为序列、嵌套结构这类本质该用 parser/状态机干的活。正则适合「简单、短、边界清晰」的校验手机号、时间窗、token不适合「任意长度、多分支」的语义匹配。写正则时默认输入是敌意的嵌套量词能不用就不用用了就加占有量词/原子组.*一律加边界Pattern必须预编译成静态常量。ReDoS 的可怕不在它难修在它「平时绿、崩时炸」所以 prevention压测畸形输入 CI lint比事后救火重要十倍。思考题Java 的Pattern是 NFA 回溯引擎Go 的regexp是线性时间引擎那是不是所有「可能被恶意输入打」的场景都应该把正则逻辑迁到用线性时间引擎实现什么情况下回溯引擎的「功能」反而是线性引擎给不了的

相关新闻

一次装齐 2005 到 2022 全系列 Visual C++ 运行库,跟 MSVCP140.dll 报错说再见

一次装齐 2005 到 2022 全系列 Visual C++ 运行库,跟 MSVCP140.dll 报错说再见

一次装齐 2005 到 2022 全系列 Visual C 运行库,跟 MSVCP140.dll 报错说再见 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 如果你的电脑总在双击某…

2026/10/15 1:12:06 阅读更多 →
向量召回变差:先查数据、切分和过滤条件

向量召回变差:先查数据、切分和过滤条件

向量召回变差:先查数据、切分和过滤条件 召回率下降不一定是向量模型变差。文档缺失、切分变化、元数据过滤和索引未完成,都可能让相关内容根本没有进入候选集。 从漏召回样本倒查 准备带人工相关性标注的查询集,记录文档版本、切分器、Em…

2026/10/1 12:15:00 阅读更多 →
缓冲区溢出攻击原理与实践:从栈溢出到现代防御机制

缓冲区溢出攻击原理与实践:从栈溢出到现代防御机制

1. 从一次“意外崩溃”说起:理解缓冲区溢出的本质那天下午,我正在调试一个用C语言写的、处理用户输入的小工具。程序很简单,就是读取用户输入的一串字符,然后回显出来。我随手输入了一长串远超我预留内存空间的字符,比…

2026/10/9 22:07:21 阅读更多 →

最新新闻

CloverBootloader 中修改 OpenSSL 源码的完整指南:从新增 API 到 make update 与 doc-nits 校验

CloverBootloader 中修改 OpenSSL 源码的完整指南:从新增 API 到 make update 与 doc-nits 校验

固件操作系统 【免费下载链接】CloverBootloader Bootloader for macOS, Windows and Linux in UEFI and in legacy mode 项目地址: https://gitcode.com/gh_mirrors/cl/CloverBootloader 点击查看 免费下载 本文以 OpenSSL 官方开发文档 HACKING.md 为主线&#x…

2026/10/15 20:28:03 阅读更多 →
NeoProgrammer:嵌入式固件原子化烧录与CI/CD交付中枢

NeoProgrammer:嵌入式固件原子化烧录与CI/CD交付中枢

简介:NeoProgrammer编程器是一套基于CH341A芯片的通用型微控制器烧录工具资源包,面向电子工程初学者、嵌入式开发爱好者及教学实践者,解决AVR/PIC/8051等主流MCU固件烧录、SPI/I2C/NOR Flash读写、OTP安全寄存器操作等实际编程需求。资源共71…

2026/10/15 20:28:03 阅读更多 →
Olares 应用分发完整指南:从 OAC 打包到 Olares Market 上架与生命周期管理

Olares 应用分发完整指南:从 OAC 打包到 Olares Market 上架与生命周期管理

云原生后端运维 【免费下载链接】Terminus Open-Source Personal Cloud OS for Always-On Agents 项目地址: https://gitcode.com/gh_mirrors/terminus2/Terminus 点击查看 免费下载 Olares(Terminus)的应用分发体系建立在开放标准与自动化校…

2026/10/15 20:28:03 阅读更多 →
NeoProgrammer:嵌入式固件交付中枢与YAML自动化烧录框架

NeoProgrammer:嵌入式固件交付中枢与YAML自动化烧录框架

简介:NeoProgrammer编程器是一套基于CH341A芯片的通用微控制器烧录工具资源包,面向电子工程师、嵌入式初学者及硬件爱好者,解决AVR/PIC/8051等主流MCU固件烧录、SPI/I2C/NOR Flash读写与OTP安全寄存器操作等实际开发需求。资源共71个文件&…

2026/10/15 20:28:03 阅读更多 →
FANUC FOCAS Linux ARM适配指南:绕过官方SDK实现CNC稳定联网

FANUC FOCAS Linux ARM适配指南:绕过官方SDK实现CNC稳定联网

简介:本资源是专为Linux与ARM嵌入式平台定制的FANUC FOCAS开发包,面向工业自动化开发者、数控系统集成工程师及工控领域二次开发人员,解决在低功耗ARM设备(如工控机、边缘网关、机器人主控)上稳定接入并控制FANUC CNC系…

2026/10/15 20:28:03 阅读更多 →
Path of Building 实操:PoB 离线构建规划器与伤害计算完整上手

Path of Building 实操:PoB 离线构建规划器与伤害计算完整上手

Path of Building 实操:PoB 离线构建规划器与伤害计算完整上手 【免费下载链接】PathOfBuilding Offline build planner for Path of Exile. 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding 规划一个 PoE 角色,往往卡在装备迭…

2026/10/15 20:27:02 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →