關於轉義符 在php正則中的匹配問題

合天网安实验室發表於2024-04-09

今天做題遇到一個很經典的問題,記錄一下,先看一段程式碼

<?php
$str,=,"\\";
$pattern,=,"/\\/";
if(preg_match($partern,$str,$arr))
{
,,,,echo,"success";
,,,,print_r($arr);
}else{
,,,,echo,"false";
}

看到這段程式碼的師傅們,思考一下,會輸出success還是false

輸出false,正則沒有被匹配到,為什麼呢?

image-20220820220805568

php對轉義符的解析

php解析正則時分為了兩個步驟,一個是php對字串的解析,之後才是對正則的解析,那麼php在解析字串時什麼時候才會將\解析為轉義呢?只有在某一字元會對這一語句產生混淆時,php才會將\解析為轉義。

分析一個正則匹配

image-20220820233147371

首先php對字串進行解析:

在這種情況下可以看到str中\並沒有被當成轉義符

而在pattern中,由於有多個\並且在正規表示式中存在/,會混淆正規表示式的邊界,因此這四個轉義符的作用分別是:

  • 第一個轉義符轉義第二個轉義符

  • 第三個轉義符轉義第四個轉義符,第五個轉義符轉義/

因此php最終解析出的str為,\/,pattern為,\\/

到preg_match時,進行正則解析(正則解析只解析正規表示式):

  • 將pattern中的,\\/,解析為\/,(第一個轉義符轉義了第二個轉義符)

經過php和正則的解析後,我們可以發現str與pattern是一樣的字串了,所以應該會輸出success,並且匹配到的部分為\/

驗證成功

image-20220820233913117

這裡提出一個問題,如果在pattern中,我的正則內容中不想使用\來轉義/,並且還想輸出success,那應該怎麼修改正則內容呢?

我們剛才提到,轉義是為了防止語句中的字元產生混淆,/與正則邊界產生了混淆,所以我們用其他的字元作為邊界就好了,比如#

image-20220820234345677

總結:在一般情況下,只有字串中的某一字元會對該語句產生混淆,這時該符號前的\才具有轉義作用。

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

這裡我在做測試有一個小坑

image-20220820234819307

首先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字串的時候我們是不是要寫四個\才可以?

image-20220821001642109

具體的解析過程我就不講了,跟上面是完全一樣的。

總結: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對字串進行解析:

  • \\被解析為\

  • \\\\,被解析為\\

經過字串解析,原本的|\\|\\\\|,變成了|\|\\|

正規表示式解析:

  • 第一個\|被解析為|

  • \\被解析為\

經過兩次解析後,最終的正規表示式變成了||\|,所以實際上是對|\進行了過濾,所以就可以使用\進行繞過了。

image-20220821004532200

因此解決的辦法是在正則過濾中不要新增\\這一項,會導致整個正規表示式直接變味。

這裡跟著原帖看發現原帖說的有點問題,自己思考了一下做出了一些猜想,發現是正確的。

image-20220821004815392

還有原帖中提到的一個問題,這裡為什麼隨便一個字串甚至是空都可以匹配成功,因為在|\\\\|的左右兩邊沒有東西,為空,所以隨便匹配都可以匹配到。

image-20220821004919804

解決方法就是兩邊加上東西就可以了。

image-20220821005133509

自己的小感想

這道題在網上的wp基本都是直接用\去執行命令,但很少有人能去討論為什麼可以這麼繞過,後端程式碼已經做出了過濾,為什麼還是會被繞過,我很幸運能夠看到更深的分析,這也是我第一次自己有獨立的想法去不斷的除錯程式碼,雖然每一次看到其他大佬wp裡不合理的地方感覺很迷茫,但是還找不到理由,但是經過不斷的除錯發現有些其他大佬的東西也不一定就都是對的,而且自己不斷除錯後找到問題有一種說不出來的成就感,總結起來就是看問題要深入,有耐心。引用原帖的一句話就是

image-20220821005705584

更多網安技能的線上實操練習,請點選這裡>>

相關文章