本系列文章主旨在于介绍一些漏洞类型产生的基本原理,探索最基础的解决问题的措施,不排除有些语言或者系统提供的安全的API可以更好地更直接地解决问题,也不排除可以严格地输入验证来解决。
正则表达式(Regular Expression)是在编码过程中常用的一种字符串匹配的技术,使用它可以带来很大的方便,也使得代码变得简短直观。它带来方便的同时,也带来一些风险。当正则表达式接受外界的输入作为正则表达式或者正则表达式的一部分时,攻击作者就可以通过将正则表达式修改为一个超级复杂的表达式,让程序在解析正则表达式时消耗大量的CPU资源,严重的可以用于发起DOS攻击。例如,比较邪恶的字符串:(.*a){x} for x \> 10,然后控制匹配的字符串是aaaaaaaaaaaaaaaaaaaaaaaa。
为什么会发生DOS攻击呢? 这还要从正则表达式的工作原理说起。
实现正则表达式的一种技术是NFA (Non-deterministic Finite Automaton)引擎,它把正则表达式翻译为一个NFA ,然后根据NFA来匹配一个输入的字符串。NFA是一个有限状态机,就是针对每一个对状态和输入都可能有多种下一个状态的可能。引擎开始根据输入进行分析转转状态直到字符串结束。由于有可能有多种下一个状态,一个决定型演算法(deterministic algorithm)就被用于分析。这种算法一个一个尝试所有的可能直到一个匹配符合条件。
例如,一个正则表达式,^(a+)+$
可以通过以下NFA来表示
对于这个正则表达式,针对输入aaaX在上图中有16种可能的路径。但是,针对aaaaaaaaaaaaaaaaX
,就会有65536中可能的路径,而且每多一个a路径的数字就会翻倍。针对一些有问题的算法,由于有太多太多路径,导致正则分析失败。
不过,也不是所有的算法都是有问题的,正则表达式也可以以一种有效的算法来实现。不幸的是,大多数正则表达式不仅尝试用于解析纯粹的正则表达式,而且在特殊情况下,还会扩展正则表达式,例如:向后引用(back-references),向后引用总是很难有效地解决。
可见邪恶的表达式对系统的影响,它们一般有一下几个特征:
1) 分组重复
2) 再重复的分组里: 重复和交替重复
例如: (a+)+ (a|aa)+, 当输入aaaaaaaaaaaaaaaaaaaaaaaaaaa时, 这些正则表达式就可能被利用。
由于很多Web技术是基于正则表达式的,例如:
在web的每一层都可能有使用正则表达式,攻击者可以利用正则表达式注入攻击WAF、浏览器甚至是Web服务器。
可能有人说,一般正则表达式都是在服务器端写好的,不会使用客户端传来的内容作为正则表达式的一部分。不过,有些API的参数可能支持正则表达式,而开发者在使用的时候没有特别注意到这一点。例如:Java的类String的public String replaceAll(String regex,String replacement)和public String[] split(String regex, int limit)的第一个参数就支持正则表达式。而且,在服务器端自己写的正则表达式,也可能在某些特殊输入的内容的情况下,引起dos攻击。
预防的方法就是:
1) 建议使用超时机制,一旦时间到,就终止正则的分析
2) 使用不回溯的正则表达式引擎,例如:RE2 , 另外RUST和GO语言也都可以保证在线性时间内结束
3) 避免使用向后引用的正则表达式,例如:嵌套的quantifiers 【(.*)*】 4) 避免使用用户的输入作为正则表达式或者正则表达式的一部分。
5) 在正式使用某个正则表达式之前,使用正则表达式工具多测试测试。
6) 如果必须使用特殊字符,需要对特殊字符进行转义处理。例如:.net的System.Text.RegularExpressions.Regex.Escape(String) 。
那么问题又来了。
如果正则表达式中的一些特殊字符,就想当做一般字符使用改怎么办? 例如,以点(.)结束的word:"word." ,如果直接匹配点(.)会被认为是匹配任意字符。 所以,一些有特殊含义的字符,就需要进行特殊处理,需要特殊处理的字符包括:<([{\^-=$!|]})?*+.>。
转义的方法有两种,
第一种是使用反斜杠转义,如下表:
特殊字符 | 转义方法 | 转义后值 |
< | 使用反斜杠 \ | \[ |
( | \{ |
第二种方法是,使用\Q 和 \E将字符包括起来,如下表:
特殊字符 | 转义方法 | 转义后值 |
< | 使用\Q和\E | \Q[\E |
( | \Q{\E |
JAVA的java.util.regex.Pattern.quote(String S) 实现了这种编码方式。
需要注意的是:由于使用反斜杠\进行转义,所以反斜杠本身又有特殊的含义了,如果出现在字符串里,也需要将反斜杠进行再转义,例如:想匹配含有*字符的字符串,那就转移成\*,在编码时,变成“\*”,这是不行的,需要写成"\\*"。
如果有不妥之处,希望可以留言指出。谢谢!
参考:
Regular expression Denial of Service - ReDoS | OWASP Foundation
Regular Expressions Reference: Quantifiers
https://en.wikipedia.org/wiki/Regular_expression
A Rough Idea of Blind Regular Expression Injection Attack – やっていく気持ち
Regex.Escape(String) Method (System.Text.RegularExpressions) | Microsoft Learn
Guide to Escaping Characters in Java RegExps | Baeldung