在本文中,我們將探討何謂HTTP響應拆分以及攻擊行為是怎樣進行的。一旦徹底理解了其發生原理(該原理往往被人所誤解),我們就可以探究如何利用響應拆分執行跨站點腳本(簡稱XSS)。接下來自然就是討論如果目標網站存在響應拆分漏洞,我們要如何利用這一機會組織CSRF(即跨站點偽造請求)攻擊。最後,我們一起來看看哪些預防措施能夠抵御這些攻擊行為。如果大家對這個話題感興趣,不妨繼續讀下去。
什麼是HTTP響應拆分?
首先讓我們設想一下某個具備多種語言選項的頁面。該頁面的默認語言為英語,但其中同時具備一個下拉菜單,允許我們在選定其中對應的其它語種後,整個頁面的語言也同時發生切換。比方說根據初始頁面的配置,302重新指向的結果為http://www.abc.com/index.php?lang=en。但對於來自德國的用戶而言,當然希望頁面內容以德語呈現,這時他們就可以從下拉菜單的備用語言中進行選擇。這使得302重新指向將被發往服務器上的德語頁面——http://www.abc.com/index.php?lang=german。用戶的浏覽器會遵循重新指向的引導,並將德語頁面正常呈現出來。
現在讓我們思考一下HTTP 302重新指向響應的主體內容。內容大體如下:
HTTP/1.1 302 Moved Temporarily
Lo
cation: http://www.abc.com/index.php?lang=en
或者是:
HTTP/1.1 302 Moved Temporarily
Location: http://www.abc.com/index.php?lang=german
大家可能已經發現了,惟一產生變化的只有lang參數的值。也就是說,這個值是由用戶所控制,我們可以將該值設置為任何想要的內容。正是這種特性導致了HTTP響應拆分攻擊的發生。
此時我們不再把參數值設定為"german",而是按照下列內容進行設定:
a) The value 'german'
b) CR/LF - %0d%0a
c) A response with Content Length 0 [這裡之所以設長度為0,是因為這一段其實無關緊要]
d) CR/LF - %0d%0a
e) A response which contains malicious content [舉例來說,可以設定JavaScript會在頁面被訪問時自動下載惡意軟件]
先來看看c)的內容--這也是首個響應。HTTP協議的工作方式是一個請求對應一個響應,因此針對該請求——即http://www.abc.com/index.php?lang=german的響應是經過精心設計的。其實我們並不關心這個響應本身及其內容,我們想要的只是將Content-Length: 0設為其響應頭。
CR/LF(即回車換行符)是響應之間的分界符。所以只要我們如d)中所示加入CR/LF內容,那麼第二輪響應即會啟動,且根據HTTP協議的規定這是完全正常的。在新一輪響應中我們可以添加大量信息。舉例來說,如果我們打算顯示一條"Hello, you have been phished"(意為'你好,你已然中招了')的消息,此時面前已經完全沒有任何阻礙了。只需輸入如下所示的內容,即可輕松實現:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 41
Hello, you have been phished
還是覺得有點迷糊?讓我們再總結一次。攻擊者控制參數並發送一個將產生兩次響應的請求;這兩次響應都由攻擊者組織,並以服務器為目標。首個響應旨在回應將頁面轉化為德語的請求,而第二個響應(到目前為止)還未經解釋,它只是暫時掛起--因為該響應還沒有能夠映射的對應請求。請記住,HTTP需要響應(無論內容代碼是什麼),但它需要的是一個能對應所有請求的響應。因此掛起中的HTTP響應是無法工作的。
現在請仔細閱讀…因為這部分正是大多數人(連我自己在很長一段時間內也是如此)沒有搞清楚的內容。為了處理第二個掛起中的響應,攻擊者會迅速對服務器上的頁面發送一條有效的公開訪問(通常是這樣)請求,比如說branches.html。
這裡假設他發送的請求為:
GET /branches.html HTTP/1.1
Host: www.abc.com
就在發出首個包含"可完全自定內容"參數的請求之後,他會旋即發出上述第二條請求,而這也正是"Hello, you have been phished"字段的映射對象。兩條請求對應兩次響應,大家明白了嗎?
哈哈,恐怕各位還是有些困惑。盡管大家可能已經了解整個映射的發生過程,但仍然沒鬧明白這種攻擊是如何影響其他人的。畢竟,攻擊者的這一切行動都發生在他自己的計算機上,所修改的請求也只限於他個人…也就是說,會受影響的只有他自己。說實話,為什麼攻擊者要對自身展開攻勢?這似乎毫無道理可言啊。不過我要指出的是,這對位於中間幀及緩存中的代理服務器或者某些設備而言,可以說是接納請求及響應的關鍵性一環。
攻擊者必須躲在代理服務器之後,並借助代理將他的請求發送到互聯網上的目標服務器處。如果他想感染其它用戶,這些被害用戶也必須處於同樣的代理服務器之後。因此,讓我們再來總結一次(請保持耐性…)
a)攻擊者發送一條包含一個值及兩次響應的請求,使用%0d%0a進行分隔。在本文的例子中,請求的內容如下:
http://www.abc.com/index.php?lang=german%0d%0aContent- Length:%200%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2041%0d%0aHello, you have been phished
b)該請求的發送目的地為www.abc.com…不過重要的是,它是通過中間代理服務器進行傳遞的。因此現在在代理服務器上,第一個請求被映射在第一次響應上,而第二次響應則由於沒有能夠匹配的請求而處於掛起狀態。
c)在首個請求發出後,攻擊者會立即向目標網站(同樣通過代理服務器)發出新請求(第二個請求),內容如下:
GET /branches.html HTTP/1.1
Host: www.abc.com
d)代理服務器會在收到branches.html後,第一時間將其映射至第二次響應中(即'You have been phished ')。因此接下來發往branches.html的請求將不再顯示銀行的分支機構名單,而是指向惡意網頁。沒錯,對於每位訪問者畢竟是如此,而不僅僅針對攻擊者。為什麼會這樣?因為這正是緩存代理服務器的處理方式…常發請求緩存響應。也就是說,如果某個發往branches.html的請求始終產生同一份關於銀行支行信息的靜態列表,那麼代理服務器幾乎肯定會調用緩存對該請求進行響應。換言之,下一次指向branches.html的請求將自動返回來自緩存的響應。而在攻擊者的安排下,代理服務器的緩存內容遭受感染,並被迫返回惡意響應而非原本的靜態列表……這種狀況將持續下去直到緩存過期。
希望經過上面的詳細介紹,大家會對HTTP響應拆分有一個明確的概念。這裡是文章的重中之重,所以不妨多讀幾遍。關鍵在於,攻擊者發出的第二條請求會迫使代理服務器遵從第二條請求對應第二次響應的映射模式。一旦理解了這一點,整個概念應該就比較清晰了。
通過響應拆分實現跨站點腳本
在這裡需要指出,我並不打算詳細解釋跨站點腳本及其具體類型。網上可以輕易找到大量討論這方面話題的文章(我會在結尾處提供一些參考鏈接),通過閱讀大家完全能夠透徹理解這一概念。
現在我們已經對響應拆分相當熟悉,那麼接下來該從攻擊者的視角出發,以獲得進一步的收獲。我們能夠通過響應拆分在目標計算機上運行JavaScript,並嘗試且最終獲取對其浏覽器的完全控制權嗎?答案是肯定的,只要對我們此前所給出的例子進行些許擴展,這一目標就可以順利實現。
大家應該還記得,我們在前文中所設計的第二輪惡意響應只是利用簡單的頁面告知用戶"你已然中招了"。而要實現更邪惡的目的,我們要編寫一些JavaScript代碼來代替原本的簡單頁面。與僅僅能夠實現顯示功能的頁面不同,JavaScript能夠切實運行於用戶的浏覽器之中。而攻擊者對於目標用戶浏覽器的控制能力完全取決於JavaScript代碼的內容。因此,如果要對前文中所設計的例子加以擴展,攻擊者會設計出如下的URL:
http://www.abc.com/index.php?lang=german%0d%0aContent- Length:%200%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent- Length:%20%0d%0aalert('在你的計算機上運行JavaScript')
然後他會像以前那樣向branches.html發送請求,該包含惡意JavaScript內容的請求自然會被映射至第二輪響應中。代理緩存將同原先一樣受到感染,而使用著同一款代理服務器的其他用戶在訪問branches.html時,該JavaScript代碼就會侵入他的計算機。
如果目標站點在XSS方面存在漏洞,那麼我們完全可以用同樣的邏輯對其展開攻擊。只是在這種情況下,漏洞參數才是腳本的主體,因此我們需要利用其替換掉前一個例子中的JavaScript部分。
在我所舉的例子中,JavaScript內容非常直觀,結果也只是彈出一個小小的警告框。事實上JavaScript完全可以編寫得更為復雜,並使攻擊者獲得可以完全掌控目標用戶浏覽器並最終控制計算機的能力。一款名為BeeF的開發框架能夠為攻擊者幫上大忙,只需編寫一小段JavaScript再將其導入BeeF控制器,受害者的計算機就在劫難逃了。
通過響應拆分實現跨站點請求偽造
與前面一樣,我不打算在這裡詳述CSRF攻擊的來龍去脈。大家還是從文末的參考資料匯總那裡去獲得相關的細節信息吧。簡單來說,CSRF攻擊的受害者會在不知情的狀態下執行某些"隱性"操作。需要強調的是,這些操作基本上都是受害者打死也不想去執行的類型。
作為CSRF攻擊發生的先決條件,受害人需要首先登錄到操作執行的站點上。因此如果攻擊者打算誘導受害者執行的操作是"刪除我的谷歌個人資料",那麼用戶必須要先登錄到相應的谷歌系統中,操作才能順利執行。另外,攻擊者必須能夠對包括參數值在內的確切結構進行預測。以銀行轉賬操作為例,具體數額必須准確有效,如:GET /transfer/php?acc1=1000&acc2=2000&amt=900。這樣只要用戶在登錄至www.abc.com之後發出類似的GET指令,由acc1指向acc2的轉賬行為將自動執行。
在CSRF攻擊當中,攻擊者總會使出某種花招誘導用戶點擊鏈接,或是采取社交角度的心理戰術、或是讓用戶訪問某個處於攻擊者控制之下的頁面;總之盡管理論上用戶是自主發出的請求,但實際上後台到底執行了什麼內容受害者並不知情。
現在,讓我們回到響應拆分這一話題。大家應該還記得攻擊者借助響應拆分特性感染branches.html頁面,並向其中注入JavaScript惡意內容以嘗試在用戶的浏覽器上運行腳本的做法吧。而在CSRF攻擊中,我們需要確保行為(例如前面提到的轉賬操作URL)在用戶訪問被感染頁面時能夠自動執行。換句話說,branches.html中會包含一個小型鏡像,並隨同該頁面一同被載入。該鏡像的<IMG SRC>標簽將向www.abc.com網站的服務器發送內容為/transfer/php?acc1=1000&acc2=2000&amt=900的請求。
因此在執行響應拆分攻擊時,整個惡意URL的內容如下所示:
http://www.abc.com/index.php?lang=german%0d%0aContent- Length:%200%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent- Length:%20%0d%0a< / body>< / html>
最終的結果是,每當用戶通過代理訪問受感染的頁面(即branches.html),且在另一個浏覽器選項卡中登錄了www.abc.com站點,那麼後台即會順利地將款項轉匯至攻擊者指定的賬戶中。
示例代碼:
現在讓我們快速浏覽一遍PHP中那些存在響應拆分漏洞的代碼類型。對於初學者來說,我們以一個小型HTML文件為例(名稱為respsplit1.html),其中包含了允許用戶選擇語言種類的下拉菜單。具體內容如下:
<HTML>
<BODY>
<FORM NAME="form" action="respsplit1.php" method="GET">
<select name="lang">
<option value="EN">English</option>
<option value="GER">German</option>
</select>
<INPUT TYPE="submit" name="Submit" value=Submit></INPUT>
</FORM>
</BODY>
</HTML>
選擇想要的語言類型後,大家當然會點擊提交按鈕,而這一輸入行為將被提交至名為respsplit1.php的PHP文件處。上述所有代碼的功能是截獲我們的輸入內容,並利用其建立一個用於重新指向的URL,再將其發送至302重新指向響應處。Respsplit1.php文件的內容如下所示:
<?php
$lang = $_GET['lang']
header("Location: http://localhost/respsplit2.php?lang=$lang");
?>
在浏覽器中打開該HTML文件並將其置於Burp或是任何其它類型的代理編輯器中。這時請留意我們在頭一個頁面點擊提交後所產生的響應。大家會清楚地發現,自己在下拉菜單中選擇的項目實際上是本機302響應中的響應頭。因此如果各位想對此進行修改,那就必須得在請求到達服務器端之前,對lang參數的值利用惡意字符串進行編輯(也就是達到我們前面所說的一項請求兩次響應)。
respsplit2.php所做的僅僅是將大家所選擇的語言種類顯示出來,除此無它。
<?php
$a=$_GET['lang'];
if (strcmp($a,'EN') == 0)
echo "Language selected is English";
elseif (strcmp($a,'GER') == 0)
echo "Language selected is German";
else
echo "No valid language selected ";
?>
現在當我打算進行上試測試時,我自己的PHP框架會從響應頭中把%0d%0a字符串除去,這種默認設置實際上起到了保護作用。不過如果大家所使用的是版本較老的框架,那麼默認設置將不會實施保護,進而導致代碼極易在CRLF處發生問題。這裡所提到的過濾功能如下圖所示:
<?php
$pattern1 = "/\%0d/";
$pattern2 = "/\%0a/";
$lang = $_GET['lang'];
$r = preg_match($pattern1 , $lang);
$s = preg_match($pattern2 , $lang);
if (($r > 0) || ($s > 0)){
echo 'Carriage Return found in user input';
echo "<BR>";
}
else {
header("Location: http://localhost/respsplit2.php?lang=$lang");
}
?>
我們可以自主編寫更為高效的過濾器,例如將內容只包含字母及數字的項目列入白名單,並直接屏蔽掉其它一切類型;但這僅僅是防御性代碼的一種使用范例。如果大家忘記編寫這類過濾器,那麼就等於將自己的命運完全交付給所使用的框架本身。如果各位與我使用的框架相似(Ubuntu 10.04內的APT庫)還好,因為其中內置了保護機制…否則就等著為此而付出慘重代價吧。
目前已經被發現了為數不少的響應拆分類漏洞。我在文末的參考鏈接中提供了詳細的清單。
應對措施:
◆響應拆分攻擊:使用服務器端驗證機制,並禁止全部用戶在任何與響應頭有關的輸入請求中使用回車換行符(即CRLF)。
◆XSS攻擊:引入白名單、黑名單過濾機制(輸入驗證)以及Escape HTML(輸出驗證)。
◆CSRF攻擊:使用AntiCsrf語言符號,這樣攻擊者就無法准確預測目標結構,自然也不能對其加以偽造了。
結論:
響應拆分式攻擊只能在多個用戶使用相同代理服務器連接不同網站時奏效。代理服務器的緩存一旦受到感染,用戶就會在從代理服務器緩存中讀取該頁面時遭到攻擊。不過請注意,並非所有代理服務器都在響應拆分方面存在漏洞,雖然這算是句題外話。如果大家有興趣進一步了解這種攻擊,我強烈建議大家閱讀由Amit Klein撰寫的精彩論文(詳見參考文獻1)。
參考文獻:
◆響應拆分攻擊- http://packetstormsecurity.org/papers/general/whitepaper_httpresponse.pdf
◆XSS攻擊 - http://www.technicalinfo.net/papers/CSS.html
◆XSS攻擊應對措施-https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet
◆CSRF攻擊 - https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29
◆CSRF攻擊應對措施- https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet
◆B浏覽器開發框架 - http://beefproject.com/
◆學習PHP(教程)- http://www.w3schools.com/php/
◆PHP函數 - http://php.net/quickref.php
◆已曝光的響應拆分漏洞匯總清單- http://cwe.mitre.org/data/definitions/113.html