譯文聲明本文是翻譯文章,文章原作者theMiddle,文章來源:http://medium.com原文地址:https://medium.com/secjuice/php-ssrf-techniques-9d422cb28d51
譯文聲明本文是翻譯文章,文章原作者theMiddle,文章來源:http://medium.com
前幾天我讀了兩篇非常棒的論文:第一篇是發表在http://blackhat.com上的「A New Era of SSRF 」,講述的是不同編程語言的SSRF問題;第二篇是由Positive Technology發表的一篇名為「PHP Wrapper」 的論文,它主要講述的是如何以多種不同的方式使用PHP Wrapper來繞過過濾器以及受過濾的輸入(您可以在結尾處找到這兩個鏈接)。
在本文中,我將深入介紹一些SSRF技術,您可以使用這些技術攻擊那些使用filter_var()或preg_match()等過濾器的PHP腳本,並且可以使用curl或file或file_get_contents()來獲取HTTP內容。
圖1:對於抓娃娃機的一種典型的SSRF攻擊
引用OWASP上的定義:
在伺服器端請求偽造(SSRF)攻擊中,攻擊者可以利用伺服器上的功能來讀取或更新內網資源。 攻擊者可以配置或更改與伺服器上運行的代碼有關的URL鏈接來讀取或提交數據,此外,通過精心構造的URL,攻擊者可以讀取伺服器配置,例如AWS元數據,連接到啟用http資料庫的內部伺服器中抑或是對內部的非公開服務發起post請求。
本文所有的實驗代碼都是基於PHP7.0.25完成的(或許當你讀到這篇文章時,它已經過時了,但描述的技術細節和原理都是有效的)。
圖2:使用的PHP版本
以下是我用來測試的PHP腳本:
<?php echo "Argument: ".$argv[1]."n"; // check if argument is a valid URL if(filter_var($argv[1], FILTER_VALIDATE_URL)) { // parse URL $r = parse_url($argv[1]); print_r($r); // check if host ends with google.com if(preg_match(/google.com$/, $r[host])) { // get page from URL exec(curl -v -s ".$r[host].", $a); print_r($a); } else { echo "Error: Host not allowed"; } } else { echo "Error: Invalid URL"; } ?>
如您所見,腳本從第一個參數中獲取URL(可以是web應用中的post方式或者get方式),然後使用函數filter_var()來驗證URL的格式。如果沒問題,解析函數parse_url()會解析URL,再使用函數preg_match()用正則表達式來檢查主機名是否以http://google.com結尾。
如果一切正常,腳本會通過curl發起一個http請求以獲取目標網頁內容,之後使用print_r()列印出響應主體。
此PHP腳本只能接受針對http://google.com 主機名的請求,其餘目標一律拒絕,不如讓我們試一試:
http://http://google.com
圖3:嘗試請求http://google.com頁面
http://http://evil.com
圖4:嘗試請求http://evil.com頁面
截止目前,一切都很順利。第一個針對http://google.com 的請求被接受,而第二個對http://evil.com的請求被拒絕。安全等級:1337+ 呵呵。
在上文我並不美觀的代碼中,正則表達用於檢驗請求主機名是否以http://google.com結尾。這似乎很難避免,但倘若你熟悉URI RFC語法,你應該明白分號和逗號可能是你利用遠程主機上的ssrf的秘密武器。
許多URL方案中都有保留字元,保留字元都有特定含義。它們在URL的方案特定部分中的外觀具有指定的語義。如果在一個方案中保留了與八位組相對應的字元,則該八位組必須被編碼。除了字元「;」, 「/」, 「?」, 「:」, 「@」, 「=」 和 「&」 被定義為保留字元,其餘一律為不保留字元。
除了分層路徑中的dot-segments之外,一般語法認為路徑段不透明。 生成應用程序的URI通常使用段中允許的保留字元來分隔scheme-specific或者dereference-handler-specific子組件。 例如分號(「;」) 和等於(「=」) 保留字元通常用於分隔適用於該段的參數和參數值。 逗號(「,」) 保留字元通常用於類似目的。
例如,一個URI生產者可能使用一個段name;v=1.1來表示對「name」版本1.1的引用,而另一個可能使用諸如「name,1.1」的段來表示相同含義。參數類型可以由scheme-specific 語義來定義,但在大多數情況下,一個參數的語法是特定的URI引用演算法的實現。
例如,若應用於主機evil.com;http://google.com可能會被curl 或者wget 解析成hostname: http://evil.com 和 querystring: http://google.com,不如來試一下:
http://evil.com;http://google.com
圖5:嘗試用 ;http://google.com bypass 過濾器
函數filter_var()可以解析許多類型的 URL schema,從上面可以看出filter_var()拒絕以主機名和「HTTP」作為schema驗證我請求的URL,但如果我把 schema從http:// 改成別的會怎樣呢?
0://evil.com;http://google.com
圖6:過濾器被使用0代替HTTP bypass掉了
完美!成功繞過filter_var() 和preg_match(),但是curl依然請求不到http://evil.com頁面。。。。為什麼呢?不如來嘗試下使用別的語法,盡量讓;http://google.com不被解析成主機名的一部分,例如通過制定目標埠:
0://evil.com:80;http://google.com:80/
圖7:SSRF使curl對http://evil.com進行了請求而不是http://google.com
耶,我們看到curl已經開始連接http://evil.com,使用逗號代替分號會出現同樣的情況 :
0://evil.com:80,http://google.com:80/
圖8:相同的SSRF不過此處使用逗號代替分號
parse_url()是用於解析一個 URL 並返回一個包含在 URL 中出現的各種組成部分關聯數組的PHP函數。這個函數並不是要驗證給定的URL,它只是將它分解成上面列出的部分。 部分網址也可以作為parse_url()的輸入並被儘可能的正確解析。
在一個PHP腳本中去bypass一個用於將部分字元串轉換為一個變數的的正則表達式是我們最喜歡研究的技術之一。這項工作是否成功最終將由Bash來認定。例如:
0://evil$http://google.com
圖9:使用「Bash中變數的語法」來繞過過濾器並利用SSRF
使用這種方式,我讓bash將$google分析為一個空變數,並且使用curl請求了evil <empty> .com。 這是不是很酷?:)
然而這隻發生在curl語法中。 實際上,正如上面的屏幕截圖所示,由parse_url()解析的主機名仍然是 evil$http://google.com。 $ google變數並沒有被解釋。 只有當使用了exec()函數而且腳本又使用$r[『host』]來創建一個curl HTTP請求時,Bash才會將其轉換為一個空變數。
顯然,這個工作只是為了防止PHP腳本使用exec()或system()函數來調用像curl,wget之類的系統命令。
另一個使用file_get_contents()代替PHP使用system()或exec()調用curl的例子:
<?php echo "Argument: ".$argv[1]."n"; // check if argument is a valid URL if(filter_var($argv[1], FILTER_VALIDATE_URL)) { // parse URL $r = parse_url($argv[1]); print_r($r); // check if host ends with google.com if(preg_match(/google.com$/, $r[host])) { // get page from URL $a = file_get_contents($argv[1]); echo($a); } else { echo "Error: Host not allowed"; } } else { echo "Error: Invalid URL"; } ?>
正如你所見,file_get_contents()在使用之前描述的相同技術驗證之後使用了原始參數變數。 讓我們嘗試通過注入一些文本來修改響應主體,如「I Love PHP」:
data://text/plain;base64,SSBsb3ZlIFBIUAo=http://google.com
圖10:嘗試控制響應主體
parse_url()不允許將文本設置為請求主機,並且它返回了「not allowed host」正確拒絕解析。不要絕望! 有一件事我們可以做,我們可以嘗試將某些東西「注入」URI的MIME類型部分……因為在這種情況下,PHP不關心MIME類型…也是,又有誰在乎呢?
data://http://google.com/plain;base64,SSBsb3ZlIFBIUAo=
圖11:向響應體注入 「I love PHP」
接下來進行XSS攻擊便是小菜一碟了…
data://http://text.google.com/plain;base64,<...b64...>
圖12:使用之前描述的技術進行簡單的XSS
以上便是全部,感謝觀看!
Positive Technologies: 「PHP Wrappers」 http://bit.ly/2lXk1e8
Orange Tsai: 「A new era of SSRF」 http://ubm.io/2FdUu9F
本文翻譯自:medium.com 如若轉載,請註明出處:medium.com安全客 - 有思想的安全新媒體
安全客徵稿啦啦啦啦~乾貨技術文章儘管投過來,獎勵豐厚
詳情請戳→https://www.anquanke.com/contribute/tips.html