正则表达式:贪婪与非贪婪
前言
在一段时间内,一直不知道.*
和.*?
之间的区别,一直单纯的觉得两者之间并没有什么区别,都是匹配任意字符,知道今天才知道其中的区别
简要介绍
首先从一个简单的问题的问题开始思考:
有这样一个字符串aaabaaab
,和这样的一个正则.*b
,那么.*
匹配的会是aaab还是aaabaaab呢?
由此问题引发出来的就是贪婪与非贪婪模式的区别,简单的说,它们之间的区别就在于贪婪模式匹配的是aaabaaab而非贪婪模式下匹配的是aaab
当然,仅仅是.*b
并不足以决定到底是按照贪婪还是非贪婪模式匹配,所以使用?来判断是否是贪婪模式
所以.*?b
就决定其使用非贪婪模式进行匹配
匹配过程
那么,具体来说,贪婪和非贪婪模式是怎么进行匹配的呢?
待匹配的字符串:"abc"
正则:".*"
贪婪模式
- 第一个"取得控制权,匹配成功,控制权交给.*
- 因为*匹配所有的字符,所以一直进行匹配,直到匹配到最后一个",发现到了字符串末尾,这时控制权交给"
- "匹配字符串末尾的",失败(因为已经由.*进行匹配),然后进行回溯
- 回溯到.,.让出一个字符"",交给"进行匹配,成功,已经匹配到字符串末尾,结束匹配
其中进行了一次回溯
非贪婪模式
- 第一个"取得控制权,匹配成功,控制权交给.*
- 因为.*可以匹配也可以不匹配,所以在非贪婪模式下不进行匹配,控制权交给"
- "匹配下一个字符a,失败,交出控制权,因为已经在正则表达式末尾,进行一次回溯
- .*进行匹配,匹配一个字符a,交出控制权
- 由"进行匹配,失败,回溯到.*
- .*进行匹配,成功,匹配b,交出控制权
- "进行匹配,失败,回溯到.*
- .*匹配c,成功,交出控制权
- "匹配最后的",成功,已经匹配到字符串末尾,结束匹配
其中进行了三次回溯
ctf
那么贪婪模式与非贪婪模式有什么用途呢?
在鸟哥的博客中,可以看到这样的代码
$reg = "/<script>.*?<\/script>/is";
$str = "<script>********</script>"; //长度大于100014
$ret = preg_replace($reg, "", $str); //返回NULL
由于使用了非贪婪模式进行匹配,所以会导致进行不断的回溯,最后由于回溯次数超过php的回溯限制,导致返回false,借此可以绕过一些ctf中的waf
php的回溯限制可以在pcre扩展中看到,默认为100000
lctf2017
在lctf2017中,可以看到这样的部分
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
if (count($matches) === 3 && $admin === $matches[0]) {
blabla
}
正则匹配字符串,如果匹配成功则将匹配结果填充进matches数组,并进行判断是否与$admin变量相同,但是进行匹配的话matches[0]的值将会是字符串本身,无法进行到下一步
所以这里输入一个非常长的字符串,让其匹配,因为在进行正则匹配的时候一定会消耗计算机资源进行一个字符的匹配(默认是贪婪模式),所以会导致php超时,使得后面的语句不会执行
Code-Breaking Puzzles
先膜p师傅Orz
代码如下
<?php
function is_php($data){
return preg_match('/<\?.*[(`;?>].*/is', $data);
}
if(empty($_FILES)) {
die(show_source(__FILE__));
}
$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']);
$data = file_get_contents($_FILES['file']['tmp_name']);
if (is_php($data)) {
echo "bad request";
} else {
@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);
}
$reg = /(.+?)+/is;
$str = str_pad("laruence", 10000, "a"); //长度为1万
$ret = preg_repalce($reg, "", $str);
这里的主要目的也是为了要绕过正则,但这里和上面不同,上面利用的是贪婪匹配导致资源耗尽
而这题使用的正是回溯
因为.的存在,且默认是贪婪模式进行匹配,所以.会一直匹配到字符串结尾,然后进行回溯,如果没有找到,会再次回溯,所以只要字符串的长度超过回溯次数,自然就能绕过正则了
后记
初次在遇到lctf那题的时候,没有仔细去思考背后的东西……只知道正则匹配是很消耗资源的,却不知道为何会消耗资源。之前也曾遇到过贪婪和非贪婪的名词,没有去了解。也层遇到过.*?的写法,没有去了解,果然,就像p师傅说的那样,不求甚解是进步的最大阻碍,下次再遇到问题时,需要更进一步思考,弄明白原理