正則表達式:貪婪與非貪婪
前言
在一段時間內,一直不知道.*
和.*?
之間的區別,一直單純的覺得兩者之間並沒有什麽區別,都是匹配任意字符,知道今天才知道其中的區別
簡要介紹
首先從一個簡單的問題的問題開始思考:
有這樣一個字符串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師傅說的那樣,不求甚解是進步的最大阻礙,下次再遇到問題時,需要更進一步思考,弄明白原理
參考鏈接
http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/
http://www.laruence.com/2010/06/08/1579.html
https://www.jb51.net/article/31491.htm
正則表達式:貪婪與非貪婪