構造HTTP請求Header實現“僞造來源IP”

構造 HTTP請求 Header 實現“僞造來源 IP ”

在閱讀本文前,大家要有一個概念,在實現正常的TCP/IP 雙方通信情況下,是無法僞造來源 IP 的,也就是說,在 TCP/IP 協議中,可以僞造數據包來源 IP ,但這會讓發送出去的數據包有去無回,無法實現正常的通信。這就像我們給對方寫信時,如果寫出錯誤的發信人地址,而收信人按信封上的發信人地址回信時,原發信人是無法收到回信的。

 

一些DDoS 攻擊,如 SYN flood,  就是利用了 TCP/ip 的此缺陷而實現攻擊的。《計算機網絡》教材一書上,對這種行爲定義爲“發射出去就不管”。

 

因此,本文標題中的僞造來源IP 是帶引號的。並非是所有 HTTP 應用程序中存在此漏洞。

 

那麼在HTTP 中, " 僞造來源 IP",  又是如何造成的?如何防禦之?

在理解這個原理之前,讀者有必要對HTTP 協議有所瞭解。 HTTP 是一個應用層協議,基於請求 / 響應模型。客戶端(往往是瀏覽器)請求與服務器端響應一一對應。

 

請求信息由請求頭和請求正文構成(在GET 請求時,可視請求正文爲空)。請求頭類似我們寫信時信封上的基本信息,對於描述本次請求的一些雙方約定。而請求正文就類似於信件的正文。服務器的響應格式,也是類似的,由響應頭信息和響應正文構成。

 

爲了解這個原理,可使用Firefox Firebug,  或 IE 瀏覽器插件 HTTPwatch 來跟蹤 HTTP 請求 / 響應數據。

 

本文中,以HTTPwatch 爲例說明之。安裝 httpwatch 並重啓 IE 瀏覽器後, IE 的工具欄上出現其圖標,點擊並運行 Httpwatch,  就會在瀏覽器下方顯示出 HTTPWatch 的主界面。

 


點擊左下角紅色的“Record ”按鈕,並在地址欄輸入 www.baidu.com,  等頁面打開後,選中一個請求,並在下方的 tab 按鈕中選擇“ Stream ”,如圖:

 


左邊即是請求數據,右邊即是服務器響應數據。左邊的請求頭均以回車換行結束,即“\r\n ” ,  最後是一個空行(內容爲 \r\n ) , 表示請求 header 結束。而請求 header 中除第一行外,其它行均由 header 名稱, header 值組成,如  Accept-Encoding: gzip, deflate , header 名稱與值之間有冒號相隔,之間的空格是可有可無的。

 

那麼,在HTTP 應用程序中,如何取得指定的請求 header 信息呢?這裏使用 PHP 語言爲例說明。對所有客戶端請求 header, PHP 程序中取得其值的方式如下: 
$_SERVER['HTTP_ HEADER_NAME '] 

HEADER_NAME應該以換成對應的 header 名稱,此項的規律是:全大寫,連接線變成下劃線。比如要取得客戶端的 User-Agent 請求頭,則使用 $_SERVER['HTTP_USER_AGENT'],  掌握這個規律,即可達到舉一反三的效果。如要取得 COOKIE 信息,則使用 $_SERVER['HTTP_COOKIE'] 即可。也就是說, $_SERVER 數組中,以HTTP 開頭的項均屬於客戶端發出的信息。

 

迴歸到HTTP 應用程序層,來源 IP 的重要性不言而語,例如表單提交限制,頻率等等均需要客戶端 IP 信息。使用流行的 Discuz X2.5  的文件 source/class/discuz/discuz_application.php 中的代碼片斷:

private function _get_client_ip() {

$ip = $_SERVER['REMOTE_ADDR'];

if (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {

$ip = $_SERVER['HTTP_CLIENT_IP'];

 

如以下的JSP代碼片段:
public String getIpAddr(HttpServletRequest request) {
       String ip = request.getHeader("x-forwarded-for");
       if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
          ip = request.getHeader("Proxy-Client-IP");
       }
       if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
          ip = request.getHeader("WL-Proxy-Client-IP");
       }
       if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
          ip = request.getRemoteAddr();
       }
       return ip;
}

 

以上代碼片段即是獲取客戶端IP ,這段程序會嘗試檢查 HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR,  根據之前的原理說明,以 HTTP_ 開頭的 header,  均屬於客戶端發送的內容。那麼,如果客戶端僞造 Client-Ip, X-Forward-For ,不就可以欺騙此程序,達到“僞造 IP ”之目的?

 

那麼如何僞造這項值?如果你會寫程序,並瞭解HTTP 協議,直接僞造請求 header 即可。或者使用 Firefox 的Moify Headers 插件即可。  



按圖示順序號輸入或點擊相應按鈕。Start 按鈕這裏變爲紅色 Stop ,說明設置成功。

這時,如果我們使用Firefox 訪問其它網站,網站服務器就針接收到我們僞造的 X-Forward-For,  值爲 1.1.1.1 。

 

嚴格意義上講,這並不是程序中的漏洞。Discuz 爲了保持較好的環境兼容性 ( 包含有反向代理的 web 服務器環境,如 nginx 作爲 php fastcgi 的前端代理 ) ,如此處理是可以理解的。那麼如何處理,才能杜絕這個問題呢?

 

服務器重新配置X-Forward-For 爲正確的值。

 

如對典型的nginx + php fastcgi 環境( nginx 與 php fastcgi 是否位於同一機器,並不妨礙此問題的產生) , nginx和 php fastcig 進程直接通信:



 

切記,$_SERVER['REMOTE_ADDR']  是由 nginx 傳遞給 php 的參數,就代表了與當前 nginx 直接通信的客戶端的 IP (是不能僞造的)。

 

再比如,存在中間層代理服務器的環境: 

 

 

這種情況下,後端的HTTP 文件服務器上獲取取的 REMOTE_ADDR 永遠是前端的 squid/varnish cache 服務器的通信 IP 。

 

服務器集羣之間的通信,是可以信任的。我們要做的就是在離用戶最近的前端代理上,強制設定X-Forward-For的值,後端所有機器不作任何設置,直接信任並使用前端機器傳遞過來的 X-Forward-For 值即可。

 

即在最前端的Nginx 上設置:

location  ~  ^/static {

proxy_pass  ....;

proxy_set_header X-Forward-For $remote_addr ;

}

 

如果最前端(與用戶直接通信)代理服務器是與php fastcgi 直接通信,則需要在其上設定:

location ~ "\.+\.php$" {

fastcgi_pass localhost:9000;

fastcgi_param  HTTP_X_FORWARD_FOR  $remote_addr;

}

記住,$remote_addr 是 nginx 的內置變量,代表了客戶端真實(網絡傳輸層) IP 。通過此項措施,強行將 X-Forward-For 設置爲客戶端 ip,  使客戶端無法通過本文所述方式“僞造 IP ”。

 

LVS轉發環境下,是否存在此問題?

LVS工作在網絡層,不改變來源及目標 IP ,更不可能更改應用層信息,故不存在此問題。如果有任何疑惑或需要幫助,請聯繫筆者信箱 [email protected]

 

存在此問題的程序:

所有版本的discuz, phpcms, phpwind, dedeCMS 。以及其它可能未知的程序。

 

例如使用Modify Headers 進行 IP 僞裝之後再登錄 bbs.phpchina.com ,我們查看自己的個人資料中的“上次訪問IP ”就發現就是我們僞裝的數據。

 

可以說,互聯網上存在此漏洞的網站實在是太多了。試試便知。那麼對於存在此漏洞,並且使用IP 作限制的網站,一定要小心。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章