今天做題遇到一個很經典的問題,記錄一下,先看一段代碼
<?php $str,=,"\\"; $pattern,=,"/\\/"; if(preg_match($partern,$str,$arr)) { ,,,,echo,"success"; ,,,,print_r($arr); }else{ ,,,,echo,"false"; }
看到這段代碼的師傅們,思考一下,會輸出success還是false
輸出false,正則沒有被匹配到,爲什麼呢?
php對轉義符的解析
php解析正則時分爲了兩個步驟,一個是php對字符串的解析,之後纔是對正則的解析,那麼php在解析字符串時什麼時候纔會將\
解析爲轉義呢?只有在某一字符會對這一語句產生混淆時,php纔會將\
解析爲轉義。
分析一個正則匹配
首先php對字符串進行解析:
在這種情況下可以看到str中\
並沒有被當成轉義符
而在pattern中,由於有多個\
並且在正則表達式中存在/
,會混淆正則表達式的邊界,因此這四個轉義符的作用分別是:
-
第一個轉義符轉義第二個轉義符
-
第三個轉義符轉義第四個轉義符,第五個轉義符轉義
/
因此php最終解析出的str爲,\/
,pattern爲,\\/
到preg_match時,進行正則解析(正則解析只解析正則表達式):
-
將pattern中的,
\\/
,解析爲\/
,(第一個轉義符轉義了第二個轉義符)
經過php和正則的解析後,我們可以發現str與pattern是一樣的字符串了,所以應該會輸出success,並且匹配到的部分爲\/
驗證成功
這裏提出一個問題,如果在pattern中,我的正則內容中不想使用\
來轉義/
,並且還想輸出success,那應該怎麼修改正則內容呢?
我們剛纔提到,轉義是爲了防止語句中的字符產生混淆,/
與正則邊界產生了混淆,所以我們用其他的字符作爲邊界就好了,比如#
總結:在一般情況下,只有字符串中的某一字符會對該語句產生混淆,這時該符號前的\
才具有轉義作用。
【---- 幫助網安學習,以下所有學習資料免費領!領取資料加 we~@x:dctintin,備註 “開源中國” 獲取!】
① 網安學習成長路徑思維導圖
② 60 + 網安經典常用工具包
③ 100+SRC 漏洞分析報告
④ 150 + 網安攻防實戰技術電子書
⑤ 最權威 CISSP 認證考試指南 + 題庫
⑥ 超 1800 頁 CTF 實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP 客戶端安全檢測指南(安卓 + IOS)
這裏我在做測試有一個小坑
首先php的字符串解析:可以看到由於字符串中並沒有可能會產生混淆語句的字符,因此\
都沒有轉義作用。
正則進行解析(只解析正則表達式,不解析其他字符串):pattern中的\/
被解析成了/
,
因此最終的正則匹配是在字符串\/
中匹配/
,因此輸出了/
這裏我一開始以爲str中的\
也發揮了轉義作用,其實並不是。
回到最初的問題,爲什麼輸出了false
<?php $str,=,"\\";, $pattern,=,"/\\/";,, if(preg_match($partern,$str,$arr)) { ,,,,echo,"success"; ,,,,print_r($arr); }else{ ,,,,echo,"false"; }
按照上面的流程分析,
首先php進行字符串解析:
-
str被解析爲
\
,pattern被解析爲\
進行正則表達式解析:
-
pattern中含有轉義符
\
,現在正則需要這個轉義符去發揮轉義作用,但在正則表達式中已經沒有其他字符去轉義了,導致了正則表達式的解析錯誤,pattern最終被解析成了什麼我們也不知道
所以最終在進行正則匹配時會輸出false
那麼我們應該怎麼讓它輸出success呢?
php正則如何正確匹配\
剛纔我們提到在正則解析時只剩下了一個\
,導致瞭解析的錯誤,那麼如果我們在正則解析這步剩下兩個\
是不是就可以在正則解析中保留下一個\
呢?再往前推,如果想要正則解析這步裏保留兩個\
,那麼在定義partern字符串的時候我們是不是要寫四個\
纔可以?
具體的解析過程我就不講了,跟上面是完全一樣的。
總結:php在正則中匹配\
時需要在正則表達式中寫入四個\
一道ctf題的分析
題目來源:[安洵杯,2019]easy_web,wp移步主頁查找,如果沒有就是還沒寫完。
if,(preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i",,$cmd)),{ ,,,,echo("forbid,~");
在這一段代碼中對傳入的cmd命令進行了過濾,並且可以看到其中有四個反斜槓,對\
做出了過濾,但最後仍然可以用反斜線逃逸,ca\t,l\s
執行命令,這是爲什麼呢?
按照我們上面所說的進行分析,首先php對字符串進行解析:
-
\\
被解析爲\
-
\\\\
,被解析爲\\
經過字符串解析,原本的|\\|\\\\|
,變成了|\|\\|
正則表達式解析:
-
第一個
\|
被解析爲|
-
\\
被解析爲\
經過兩次解析後,最終的正則表達式變成了||\|
,所以實際上是對|\
進行了過濾,所以就可以使用\
進行繞過了。
因此解決的辦法是在正則過濾中不要添加\\
這一項,會導致整個正則表達式直接變味。
這裏跟着原帖看發現原帖說的有點問題,自己思考了一下做出了一些猜想,發現是正確的。
還有原帖中提到的一個問題,這裏爲什麼隨便一個字符串甚至是空都可以匹配成功,因爲在|\\\\|
的左右兩邊沒有東西,爲空,所以隨便匹配都可以匹配到。
解決方法就是兩邊加上東西就可以了。
自己的小感想
這道題在網上的wp基本都是直接用\
去執行命令,但很少有人能去討論爲什麼可以這麼繞過,後端代碼已經做出了過濾,爲什麼還是會被繞過,我很幸運能夠看到更深的分析,這也是我第一次自己有獨立的想法去不斷的調試代碼,雖然每一次看到其他大佬wp裏不合理的地方感覺很迷茫,但是還找不到理由,但是經過不斷的調試發現有些其他大佬的東西也不一定就都是對的,而且自己不斷調試後找到問題有一種說不出來的成就感,總結起來就是看問題要深入,有耐心。引用原帖的一句話就是