从一次CPU打满到ReDos攻击和防范

一次,cpu,满到,redos,攻击,防范 · 浏览次数 : 30

小编点评

**ReDoS 攻击原理** ReDoS 攻击是一种正则表达式拒绝服务攻击,它通过构造复杂的正则表达式,导致正则表达式引擎在匹配字符串过程中消耗大量内存和 CPU 资源,最终导致服务器资源被耗尽,导致系统无法响应。 **常见场景** * 使用 javax.validation.constraints.Pattern 验证入参是否合理的场景 * 使用 String.matches 进行业务数据验证的场景 * 使用 String.replaceAll 做参数替换的场景 * 配置文件匹配参数的场景 **防范 ReDoS 攻击** * 尽量减少正则在业务中的使用场景 * 多做测试,增加服务器的性能监控 * 使用静态代码分析工具增加性能监控 * 防范正则表达式导致的 ReDoS 问题

正文

作者:京东物流 刘海茂

近期碰到一起值班报警事件,web 应用服务器 CPU 消耗打到 99%,排查后发现是因为 ReDoS 导致了服务器发生了资源被耗尽、访问系统缓慢的问题,通过排查过程从而分享下 ReDos 攻击的原理、常见场景以及防范和解决方案,如果有错误欢迎指正。

背景

值班的时候突然报警,web 应用服务器 CPU 消耗打到 99%,同时现场反馈系统访问缓慢

登录泰山平台,查看 ump 监控发现系统消耗 CPU 消耗突然被打满

通过 java 自带的 dump 工具,下载 jstock 文件,发现有大量相同任务线程在运行,具体的堆栈信息如下

仔细查看这些线程的执行代码,发现都调用了 UrlUtil.extractDomain 这个方法

根据堆栈信息查看业务代码,发现是 joybuy 登录拦截器用正则表达式匹配访问 url 解析主域的方法出现了阻塞,至此,可以判断是因为 ReDoS 导致了服务器发生了资源被耗尽、访问系统缓慢的问题,那么,什么是 ReDoS 呢?

ReDos 简介

ReDoS 攻击(正则表达式拒绝服务攻击 (Regular Expression Denial of Service)),攻击者可构造特殊的字符串,导致正则表达式运行会消耗大量的内存和 cpu 导致服务器资源被耗尽。无法继续响应,那为何不确定的正则表达式会导致 redos 攻击呢?这得从正则表达式的实现原理说起

原理

目前实现正则表达式引擎的方式有两种

  • DFA 自动机(Deterministic Finite Automaton,确定有限状态自动机)
  • NFA 自动机(Nondeterministic Finite Automaton,非确定有限状态自动机)
  • DFA 自动机的构造代价远大于 NFA 自动机,但 DFA 自动机的执行效率高于 NFA 自动机
  • 假设一个字符串的长度为 n,如果采用 DFA 自动机作为正则表达式引擎,则匹配的时间复杂度为 O (n)
  • 如果采用 NFA 自动机作为正则表达式引擎,NFA 自动机在匹配过程中存在大量的分支和回溯,假设 NFA 的状态数为 s,
  • 则匹配的时间复杂度为 O(ns)
  • NFA 自动机的优势是支持更多高级功能,但都是基于子表达式独立进行匹配
  • 因此在编程语言里,使用的正则表达式库都是基于 NFA 自动机实现的

NFA 的特性:

  1. 一个有限的状态集合 S

  2. 一个输入符号集合 sigma,空字符 epsilon 不属于 Sigma

  3. 状态迁移函数 F,对于特定的输入字符和状态,输出对应的变更状态集合

4.s0 为初始状态

5.S 子集为结束状态集

说明

定义一个正则表达式 ^(a+)+$ 来对字符串 aaaaX 匹配。使用 NFA 的正则引擎,必须经历 2^4=16 次尝试失败后才能否定这个匹配。

同理字符串为 aaaaaaaaaaX 就要经历 2^10=1024 次尝试。如果我们继续增加 a 的个数为 20 个、30 个或者更多,那么这里的匹配会变成指数增长

常见 ReDoS 场景

以 java 为例,有以下几种常见的 ReDoS 场景:

1、使用 javax.validation.constraints.Pattern 验证入参是否合理的场景

/**
 * 客户备注
 * */
@ExcelProperty(index = 14)
@Length(min = 11 , max = 11, message = "VAT号必须为11位")
@Pattern(regexp = "^(GB)\d{9}", message = "VAT号必须以GB开头,9位数字结尾")
private String vatNumber;

2、使用 String.matches 进行业务数据验证的场景

//发票日期格式yyyy-MM-dd
String regExp = "^[1-9]\d{3}-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2][0-9]|3[0-1])$";
if (StringUtils.isNotBlank(outstockDto.getInvoiceDate()) && !outstockDto.getInvoiceDate().matches(regExp)){
    totalMsg.add(new ErrorMsgDTO(ResultCodeEnum.OUTSTOCK_INVOICE_DATE_FORMAT_ERROR.getCode()));
}

3、使用 String.replaceAll 做参数替换的场景

private String getParamName(String str) {
    if (PATTERN_START_END.matcher(str).matches()) {
        String newStr = str.replaceAll("#\{", "").replaceAll("\}", "");
        if (StringUtils.isEmpty(newStr)) {
            return "";
        } else if (newStr.contains(".")) {
            return StringUtils.substringAfterLast(newStr, ".");
        }
        return newStr;
    }
    return null;
}

4、配置文件匹配参数的场景

# joybuy登录主域
joybuy.login.domain = .*fop.joybuy.com$
# 欧美B账号登录主域
pulsar.login.domain = .*ifop.jd.com$

ReDoS 检测

1、RegexStaticAnalysis 工具

测试方式如下:

使用 maven package 打包后执行本地运行,输入需要测试的正则表达式

2、在线测试地址:https://regex101.com/

测试方式:

直接在输入框输入正则表达式和需要测试的字符串,既可以看到对饮匹配的步数和结果

在 dubugger 模式下可以查看匹配的详细过程和步数

防范手段

防范手段只是为了降低风险而不能百分百消除 ReDoS 这种威胁。当然为了避免这种威胁的最好手段是尽量减少正则在业务中的使用场景或者多做测试,增加服务器的性能监控等

  • 降低正则表达式的复杂度,尽量少用分组
  • 严格限制用户输入的字符串长度
  • 使用单元测试、fuzzing 测试保证安全
  • 使用静态代码分析工具
  • 增加性能监控,如 ump、pfinder 等

解决方法

了解了 ReDoS 的原理和防范,针对本次 CPU 的报警代码进行了优化,采用判断请求路径和分割字符串的方式获取访问的域,避免使用正则表达式导致的 ReDoS 问题

实际修复代码

public static String extractDomain(String url) {
    if(StringUtils.isBlank(url)) {
        return "";
    }
    int index = 0;
    if(url.startsWith(HTTP)) {
        index = HTTP.length();
    } else if(url.startsWith(HTTPS)) {
        index = HTTPS.length();
    } else {
        return "";
    }
    String safeUrl = url.substring(index);
    index = safeUrl.indexOf('/');
    if(index > 0) {
        safeUrl = safeUrl.substring(0, index);
    }
    String[] array = safeUrl.split("\.");
    if(array.length < 2) {
        return "";
    }
    String part1 = array[array.length - 2];
    String part2 = array[array.length - 1];


    if(StringUtils.isNotBlank(part1) && StringUtils.isNotBlank(part2)) {
        if(!isIn(part2, DOMAINS)) {
            return "";
        }
        return part1 + '.' + part2;
    }
    return "";
}

与从一次CPU打满到ReDos攻击和防范相似的内容:

从一次CPU打满到ReDos攻击和防范

web 应用服务器 CPU 消耗打到 99%,排查后发现是因为 ReDoS 导致了服务器发生了资源被耗尽、访问系统缓慢的问题。本片文章主要介绍ReDos 攻击的原理、常见场景以及防范和解决方案。

CGLIB动态代理对象GC问题排查

## 一、问题是怎么发现的 最近有个新系统开发完成后要上线,由于系统调用量很大,所以先对核心接口进行了一次压力测试,由于核心接口中基本上只有纯内存运算,所以预估核心接口的压测QPS能够达到上千。 压测容器配置:4C8G 先从10个并发开始进行发压,结果cpu一下就飙升到了100%,但是核心接口的qp

[转帖]Linux性能分析(二):理解CPU上下文切换

在计算机中,上下文切换是指存储进程或线程的状态,以便以后可以还原它并从同一点恢复执行。这允许多个进程共享一个CPU,这是多任务操作系统的基本功能。 Linux 是一个多任务操作系统,它支持远大于 CPU 数量的任务同时运行,这依赖于CPU上下文切换。CPU 上下文切换,就是先把前一个任务的 CPU

从“一云多芯”支持,看多元算力的全栈云方案

摘要:华为云Stack如何在不同CPU架构下,构建信创云平台多元算力的全栈解决方案?本文将为你具体阐释。 本文分享自华为云社区《从“一云多芯”支持,看多元算力的全栈云方案》,作者:徐安 华为云Stack资深架构师。 背景 华为云Stack作为华为云在政企市场的品牌,是政企客户智能升级的首选平台。随着

[转帖]电脑硬件入门——基础之CPU架构解读

https://zhuanlan.zhihu.com/p/65840506 前言 上一篇文章我们从整体上介绍了电脑的各个部件以及功能,CPU可以说是整台电脑中最核心的部件。这篇文章里面,给大家介绍一下CPU里面都有什么。 我们家里用的电脑,CPU只有两家厂商在生产:Intel和AMD。每家厂商都提供

[转贴]CPU设计全流程-以Alpha为例

https://zhuanlan.zhihu.com/p/529872958 1、前言 作为一种超大规模集成电路,CPU在过去几十年里始终遵循摩尔定律——每过十八到二十四个月,硅片单位面积上晶体管数量翻倍。从产业界的表现来看,每过一到两年,英特尔、AMD和IBM等公司就会推出自己的新产品。 那么问题

从Linux零拷贝深入了解I/O

转载&学习文章:从Linux零拷贝深入了解I/O 本文将从文件传输场景以及零拷贝技术深究 Linux I/O 的发展过程、优化手段以及实际应用。 前言 存储器是计算机的核心部件之一,在完全理想的状态下,存储器应该要同时具备以下三种特性: 速度足够快:存储器的存取速度应当快于 CPU 执行一条指令,这

指令集,架构,软核,硬核的简单学习

指令集,架构,软核,硬核的简单学习 前言 最近一直研究国产化信创知识. 之前的很多总结都比较底层. 今天想着从稍微高一点的角度,尤其是CPU厂商的角度进行分析. 看了很多知乎大神,比如guee等的知识,很有感触. 也想借着周末的时间,学习与总结一下. 国产化的脉络 国产计算机其实从上世纪五十年代就开

从零在win10上测试whisper、faster-whisper、whisperx在CPU和GPU的各自表现情况

Anaconda是什么? Anaconda 是一个开源的 Python 发行版本,主要面向数据科学、机器学习和数据分析等领域。它不仅包含了 Python 解释器本身,更重要的是集成了大量的用于科学计算、数据分析和机器学习相关的第三方库,并且提供了一个强大的包管理和环境管理工具——Conda。 通过C

[转帖]通过实战理解CPU上下文切换

Linux是一个多任务的操作系统,可以支持远大于CPU数量的任务同时运行,但是我们都知道这其实是一个错觉,真正是系统在很短的时间内将CPU轮流分配给各个进程,给用户造成多任务同时运行的错觉。所以这就是有一个问题,在每次运行进程之前CPU都需要知道进程从哪里加载、从哪里运行,也就是说需要系统提前帮它设