PCRE回溯次數(shù)繞過安全限制的正則解析
這次Code-Breaking Puzzles中我出了一道看似很簡(jiǎn)單的題目,將其代碼簡(jiǎn)化如下:
<?php function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); } if(!is_php($input)) { // fwrite($f, $input); ... }
大意是判斷一下用戶輸入的內(nèi)容有沒有PHP代碼,如果沒有,則寫入文件。這種時(shí)候,如何繞過is_php()函數(shù)來寫入webshell呢?
這道題看似簡(jiǎn)單,深究其原理,還是值得寫一篇文章的。
正則表達(dá)式是什么
正則表達(dá)式是一個(gè)可以被“有限狀態(tài)自動(dòng)機(jī)”接受的語言類。
“有限狀態(tài)自動(dòng)機(jī)”,其擁有有限數(shù)量的狀態(tài),每個(gè)狀態(tài)可以遷移到零個(gè)或多個(gè)狀態(tài),輸入字串決定執(zhí)行哪個(gè)狀態(tài)的遷移。
而常見的正則引擎,又被細(xì)分為DFA(確定性有限狀態(tài)自動(dòng)機(jī))與NFA(非確定性有限狀態(tài)自動(dòng)機(jī))。他們匹配輸入的過程分別是:
- DFA: 從起始狀態(tài)開始,一個(gè)字符一個(gè)字符地讀取輸入串,并根據(jù)正則來一步步確定至下一個(gè)轉(zhuǎn)移狀態(tài),直到匹配不上或走完整個(gè)輸入
- NFA:從起始狀態(tài)開始,一個(gè)字符一個(gè)字符地讀取輸入串,并與正則表達(dá)式進(jìn)行匹配,如果匹配不上,則進(jìn)行回溯,嘗試其他狀態(tài)
由于NFA的執(zhí)行過程存在回溯,所以其性能會(huì)劣于DFA,但它支持更多功能。大多數(shù)程序語言都使用了NFA作為正則引擎,其中也包括PHP使用的PCRE庫。
回溯的過程是怎樣的
所以,我們題目中的正則<\?.*[(`;?>].*
,假設(shè)匹配的輸入是<?php phpinfo();//aaaaa,實(shí)際執(zhí)行流程是這樣的:
見上圖,可見第4步的時(shí)候,因?yàn)榈谝粋€(gè).*
可以匹配任何字符,所以最終匹配到了輸入串的結(jié)尾,也就是//aaaaa
。但此時(shí)顯然是不對(duì)的,因?yàn)檎齽t顯示.*
后面還應(yīng)該有一個(gè)字符[(`;?>]
。
所以NFA就開始回溯,先吐出一個(gè)a
,輸入變成第5步顯示的//aaaa
,但仍然匹配不上正則,繼續(xù)吐出a
,變成//aaa
,仍然匹配不上……
最終直到吐出;
,輸入變成第12步顯示的<?php phpinfo()
,此時(shí),.*
匹配的是php phpinfo()
,而后面的;
則匹配上[(`;?>]
,這個(gè)結(jié)果滿足正則表達(dá)式的要求,于是不再回溯。13步開始向后匹配;
,14步匹配.*
,第二個(gè).*
匹配到了字符串末尾,最后結(jié)束匹配。
在調(diào)試正則表達(dá)式的時(shí)候,我們可以查看當(dāng)前回溯的次數(shù):
這里回溯了8次。
PHP的pcre.backtrack_limit限制利用
PHP為了防止正則表達(dá)式的拒絕服務(wù)攻擊(reDOS),給pcre設(shè)定了一個(gè)回溯次數(shù)上限pcre.backtrack_limit。我們可以通過var_dump(ini_get('pcre.backtrack_limit'));的方式查看當(dāng)前環(huán)境下的上限:
這里有個(gè)有趣的事情,就是PHP文檔中,中英文版本的數(shù)值是不一樣的:
我們應(yīng)該以英文版為參考。
可見,回溯次數(shù)上限默認(rèn)是100萬。那么,假設(shè)我們的回溯次數(shù)超過了100萬,會(huì)出現(xiàn)什么現(xiàn)象呢?比如:
可見,preg_match返回的非1和0,而是false。
preg_match函數(shù)返回false表示此次執(zhí)行失敗了,我們可以調(diào)用var_dump(preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR);發(fā)現(xiàn)失敗的原因的確是回溯次數(shù)超出了限制:
所以,這道題的答案就呼之欲出了。我們通過發(fā)送超長(zhǎng)字符串的方式,使正則執(zhí)行失敗,最后繞過目標(biāo)對(duì)PHP語言的限制。
對(duì)應(yīng)的POC如下:
import requests from io import BytesIO files = { 'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000) } res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False) print(res.headers)
PCRE另一種錯(cuò)誤的用法
延伸一下,很多基于PHP的WAF,如:
if(preg_match('/SELECT.+FROM.+/is', $input)) { die('SQL Injection'); }
均存在上述問題,通過大量回溯可以進(jìn)行繞過。
另外,我遇到更常見的一種WAF是:
if(preg_match('/UNION.+?SELECT/is', $input)) { die('SQL Injection'); }
這里涉及到了正則表達(dá)式的“非貪婪模式”。在NFA中,如果我輸入U(xiǎn)NION/*aaaaa*/SELECT,這個(gè)正則表達(dá)式執(zhí)行流程如下:
.+?
匹配到/
- 因?yàn)榉秦澙纺J?,所?code>.+?停止匹配,而由
S
匹配*
S
匹配*
失敗,回溯,再由.+?
匹配*
- 因?yàn)榉秦澙纺J?,所?code>.+?停止匹配,而由
S
匹配a
S
匹配a
失敗,回溯,再由.+?
匹配a
- ...
回溯次數(shù)隨著a的數(shù)量增加而增加。所以,我們?nèi)匀豢梢酝ㄟ^發(fā)送大量a,來使回溯次數(shù)超出pcre.backtrack_limit限制,進(jìn)而繞過WAF:
修復(fù)方法
那么,如何修復(fù)這個(gè)問題呢?
其實(shí)如果我們仔細(xì)觀察PHP文檔,是可以看到preg_match函數(shù)下面的警告的:
如果用preg_match對(duì)字符串進(jìn)行匹配,一定要使用===全等號(hào)來判斷返回值,如:
function is_php($data){ return preg_match('/<\?.*[(`;?>].*/is', $data); } if(is_php($input) === 0) { // fwrite($f, $input); ... }
這樣,即使正則執(zhí)行失敗返回false,也不會(huì)進(jìn)入if語句。
以上就是PCRE回溯次數(shù)繞過安全限制的正則解析的詳細(xì)內(nèi)容,更多關(guān)于PCRE回溯次數(shù)繞過安全限制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PHP錯(cuò)誤Allowed memory size of 67108864 bytes exhausted的3種解決辦法
這篇文章主要介紹了PHP錯(cuò)誤Allowed memory size of 67108864 bytes exhausted的3種解決辦法,PHP的內(nèi)存溢出錯(cuò)誤,需要的朋友可以參考下2014-07-07將博客園(cnblogs.com)數(shù)據(jù)導(dǎo)入到wordpress的代碼
博客園限制太多,于是決定從博客園(cnblogs)更換自己個(gè)人的博客。WORDPRESS口碑還不錯(cuò),于是決定用用看。之前發(fā)的數(shù)百篇日志需要導(dǎo)入過來,在網(wǎng)上搜了一會(huì),發(fā)現(xiàn)沒有這個(gè)插件,無奈只能自己寫一個(gè)2013-01-01使用swoole 定時(shí)器變更超時(shí)未支付訂單狀態(tài)的解決方案
本文主要是借助 swoole 定時(shí)器和 redis 的 zset 來實(shí)現(xiàn)的定時(shí)檢查并過期未支付訂單,感興趣的朋友跟隨小編一起看看吧2019-07-07thinkPHP中create方法與令牌驗(yàn)證實(shí)例淺析
這篇文章主要介紹了thinkPHP中create方法與令牌驗(yàn)證,以一個(gè)簡(jiǎn)單實(shí)例形式分析了thinkPHP中create方法與令牌驗(yàn)證增加表單安全性的相關(guān)技巧,代碼備有詳盡注釋說明,需要的朋友可以參考下2015-12-12Swoole?webSocket消息服務(wù)系統(tǒng)壓力測(cè)試解析
這篇文章主要為大家介紹了Swoole?webSocket消息服務(wù)系統(tǒng)壓力測(cè)試解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03thinkPHP5.0框架應(yīng)用請(qǐng)求生命周期分析
這篇文章主要介紹了thinkPHP5.0框架應(yīng)用請(qǐng)求生命周期,較為詳細(xì)的分析了thinkPHP5.0框架應(yīng)用請(qǐng)求生命周期所涉及的各個(gè)執(zhí)行流程,需要的朋友可以參考下2017-03-03