轉:造HTTP請求Header實現「僞造來源IP」

構造 HTTP請求 Header 實現「僞造來源 IP 」 php

 

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

 

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

 

所以,本文標題中的僞造來源IP 是帶引號的。並不是是全部 HTTP 應用程序中存在此漏洞。 web

 

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

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

 

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

 

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

 

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

 


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

 


左邊便是請求數據,右邊便是服務器響應數據。左邊的請求頭均以回車換行結束,即「\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 ,更不可能更改應用層信息,故不存在此問題。若是有任何疑惑或須要幫助,請聯繫筆者信箱 zhangxugg@163.com。

 

存在此問題的程序:

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

 

例如使用Modify Headers 進行 IP 假裝以後再登陸 bbs.phpchina.com ,咱們查看本身的我的資料中的「上次訪問 IP 」就發現就是咱們假裝的數據。

 

能夠說,互聯網上存在此漏洞的網站實在是太多了。試試便知。那麼對於存在此漏洞,而且使用IP 做限制的網站,必定要當心。

相關文章
相關標籤/搜索