1. 程式人生 > >服務端獲取客戶端ip方法

服務端獲取客戶端ip方法

X-Forwarded-For

X-Forwarded-For:簡稱XFF頭,它代表客戶端,也就是HTTP的請求端真實的IP,只有在通過了HTTP 代理或者負載均衡伺服器時才會新增該項。它不是RFC中定義的標準請求頭資訊,在squid快取代理伺服器開發文件中可以找到該項的詳細介紹。標準格式如下:X-Forwarded-For: client1, proxy1, proxy2。

外文名 X-Forwarded-For 代    表 客戶端
簡    稱 XFF頭 作    用 獲得HTTP請求端真實的IP

 

產生背景

X-Forwarded-For(XFF)是用來識別通過HTTP代理或負載均衡方式連線到Web伺服器的客戶端最原始的IP地址的HTTP請求頭欄位。 Squid 快取代理伺服器的開發人員最早引入了這一HTTP頭欄位,並由IETF在Forwarded-For HTTP頭欄位標準化草案中正式提出。

當今多數快取伺服器的使用者為大型ISP,為了通過快取的方式來降低他們的外部頻寬,他們常常通過鼓勵或強制使用者使用代理伺服器來接入網際網路。有些情況下, 這些代理伺服器是透明代理, 使用者甚至不知道自己正在使用代理上網。

如果沒有XFF或者另外一種相似的技術,所有通過代理伺服器的連線只會顯示代理伺服器的IP地址(而非連線發起的原始IP地址), 這樣的代理伺服器實際上充當了匿名服務提供者的角色, 如果連線的原始IP地址不可得,惡意訪問的檢測與預防的難度將大大增加。XFF的有效性依賴於代理伺服器提供的連線原始IP地址的真實性,因此, XFF的有效使用應該保證代理伺服器是可信的, 比如可以通過建立可信伺服器白名單的方式。

格式

這一HTTP頭一般格式如下:

X-Forwarded-For: client1, proxy1, proxy2, proxy3

其中的值通過一個 逗號+空格 把多個IP地址區分開, 最左邊(client1)是最原始客戶端的IP地址, 代理伺服器每成功收到一個請求,就把請求來源IP地址

新增到右邊。 在上面這個例子中,這個請求成功通過了三臺代理伺服器:proxy1, proxy2 及 proxy3。請求由client1發出,到達了proxy3(proxy3可能是請求的終點)。請求剛從client1中發出時,XFF是空的,請求被髮往proxy1;通過proxy1的時候,client1被新增到XFF中,之後請求被髮往proxy2;通過proxy2的時候,proxy1被新增到XFF中,之後請求被髮往proxy3;通過proxy3時,proxy2被新增到XFF中,之後請求的的去向不明,如果proxy3不是請求終點,請求會被繼續轉發。

鑑於偽造這一欄位非常容易,應該謹慎使用X-Forwarded-For欄位。正常情況下XFF中最後一個IP地址是最後一個代理伺服器的IP地址, 這通常是一個比較可靠的資訊來源。

使用

在代理轉發及反向代理中經常使用X-Forwarded-For 欄位。

代理轉發

在 代理轉發的場景中,你可以通過內部代理鏈以及記錄在閘道器裝置上的IP地址追蹤到網路中客戶端的IP地址。處於安全考慮,閘道器裝置在把請求傳送到外網(因特網)前,應該去除 X-Forwarded-For 欄位裡的所有資訊。這種情況下所有的資訊都是在你的內部網路內生成,因此X-Forwarded-For欄位中的資訊應該是可靠的。

反向代理

在反向代理的情況下,你可以追蹤到網際網路上連線到你的伺服器的客戶端的IP地址, 即使你的網路伺服器和網際網路在路由上是不可達的。這種情況下你不應該信任所有X-Forwarded-For資訊,其中有部分可能是偽造的。因此需要建立一個信任白名單來確保X-Forwarded-For中哪些IP地址對你是可信的。

最後一次代理伺服器的地址並沒有記錄在代理鏈中,因此只記錄 X-Forwarded-For 欄位是不夠的。完整起見,Web伺服器應該記錄請求來源的IP地址以及X-Forwarded-For 欄位資訊。

 

    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                //可能是代理伺服器的ip
                ipAddress = request.getRemoteAddr();
                //本機訪問獲取ip
                if (ipAddress.equals("127.0.0.1")) {
                    //根據網絡卡取本機配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            //對於通過多個代理的情況,第一個IP為客戶端真實IP,多個IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        
        return ipAddress;
    }

 

因為存在很多種網路結構,如 Nginx+Resin、Apache+WebLogic、Squid+Nginx。下面挨個兒講一下。

首先,明確一下,Nginx 配置一般如下所示:

              location / {
                       proxy_pass       http://yourdomain.com;
                       proxy_set_header   Host             $host;
                       proxy_set_header   X-Real-IP        $remote_addr;
                       proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
              }

注意看紅色字型,這些配置與下面的闖關拿IP有關。

 

———————————————————————————————

——第一關|X-Forwarded-For :背景——

這是一個 Squid 開發的欄位,並非 RFC 標準。

簡稱 XFF 頭,只有在通過了 HTTP 代理或者負載均衡伺服器時才會新增該項。在 Squid 開發文件中可以找到該項的詳細介紹。

XFF 格式如下:

X-Forwarded-For: client1, proxy1, proxy2

可以看出,XFF 頭資訊可以有多個,中間用逗號分隔,第一項為真實的客戶端ip,剩下的就是曾經經過的代理或負載均衡伺服器的ip地址。

 

——第一關|X-Forwarded-For :場景=客戶端--CDN--Nginx——

當用戶請求經過 CDN 後到達 Nginx 負載均衡伺服器時,其 XFF 頭資訊應該為 “客戶端IP,CDN的IP”。

一般情況下CDN服務商出於自身安全考慮會將遮蔽CDN的ip,只保留客戶端ip。

那麼請求頭到達 Nginx 時:

  • 在預設情況下,Nginx 並不會對 XFF 頭做任何處理
    • 此時 Nginx 後面的 Resin/Apache/Tomcat 通過 request.getHeader("X-FORWARDED-FOR") 獲得的ip仍然是原始ip
  • 當 Nginx 設定 X-Forwarded-For 等於 $proxy_add_x_forwarded_for 時:
    • 如果從CDN過來的請求沒有設定 XFF 頭(通常這種事情不會發生),XFF 頭為 CDN 的ip
      • 此時相對於 Nginx 來說,客戶端就是 CDN 
    • 如果 CDN 設定了 XFF 頭,我們這裡又設定了一次,且值為$proxy_add_x_forwarded_for 的話:
      • XFF 頭為“客戶端IP,Nginx負載均衡伺服器IP”,這樣取第一個值即可
      • 這也就是大家所常見的場景!

http://images.cnblogs.com/cnblogs_com/zhengyun_ustc/255879/o_client-cdn-nginx-resin.png

綜上所述,XFF 頭在上圖的場景,Resin 通過 request.getHeader("X-FORWARDED-FOR") 獲得的ip字串,做一個split,第一個元素就是原始ip。

那麼,XFF 頭可以偽造嗎?

 

——第一關|X-Forwarded-For :偽造——

可以偽造。

XFF 頭僅僅是 HTTP Headers 中的一分子,自然是可以隨意增刪改的。如附錄A所示。

很多投票系統都有此漏洞,它們簡單地取 XFF 頭中定義的ip地址設定為來源地址,因此第三方可以偽造任何ip投票。

 

———————————————————————————————

——第二和第三關|Proxy-Client-IP/WL-Proxy-Client-IP :背景——

Proxy-Client-IP 欄位和 WL-Proxy-Client-IP 欄位只在 Apache(Weblogic Plug-In Enable)+WebLogic 搭配下出現,其中“WL” 就是 WebLogic 的縮寫。

即訪問路徑是:

Client -> Apache WebServer + Weblogic http plugin -> Weblogic Instances

所以這兩關對於我們來說僅僅是相容而已,怕你突然把 Nginx+Resin 換成 Apache+WebLogic 。

也可以直接忽略這兩個欄位。

 

———————————————————————————————

——第四關|HTTP-Client-IP :背景——

HTTP_CLIENT_IP 是代理伺服器傳送的HTTP頭。

很多時候 Nginx 配置中也並沒有下面這項:

proxy_set_header HTTP_CLIENT_IP $remote_addr;

所以本關也可以忽略。

———————————————————————————————

——第五關| request.getRemoteAddr() :背景——

從 request.getRemoteAddr() 函式的定義看:

    Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. 

實際上,REMOTE_ADDR 是客戶端跟伺服器“握手”時的IP,但如果使用了“匿名代理”,REMOTE_ADDR 將顯示代理伺服器的ip,或者最後一個代理伺服器的ip。請參考附錄B。 

 

綜上,

java/php 裡拿到的ip地址可能是偽造的或代理伺服器的ip。

 

+++附錄A XFF 與 Nginx 配置的測試用例+++

測試環境: nginx+resin
內網IP:172.16.100.10
客戶端IP:123.123.123.123

測試頁面: test.jsp
<%
out.println("x-forwarded-for: " + request.getHeader("x-forwarded-for"));
out.println("remote hosts: " + request.getRemoteAddr());
%>
 

nginx 配置一

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

 

wget測試

wget -O aa --header="X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp"

頁面返回結果:

x-forwarded-for: 192.168.0.1, 123.123.123.123

remote hosts: 172.16.100.10

 

curl測試

curl -H "X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp"

x-forwarded-for: 192.168.0.1, 123.123.123.123

remote hosts: 172.16.100.10


nginx 配置二
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

wget測試:
wget -O aa --header="X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp"
頁面返回結果:
x-forwarded-for: 123.123.123.123
remote hosts: 172.16.100.10

curl測試
curl -H "X-Forwarded-For:192.168.0.1" "http://test.com/test.jsp"
x-forwarded-for: 123.123.123.123
remote hosts: 172.16.100.10

測試結果:
1、配置  

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
增加了一個真實ip X-Forwarded-For,並且順序是增加到了“後面”。

2、配置  

proxy_set_header X-Forwarded-For $remote_addr;
清空了客戶端偽造傳入的X-Forwarded-For,
保證了使用 request.getHeader("x-forwarded-for") 獲取的ip為真實ip,
或者用“,”分隔,擷取 X-Forwarded-For 最後的值。

 

+++附錄B 搜狗瀏覽器高速模式的測試用例+++

訪問路徑:

搜狗瀏覽器“高速”模式(即使用代理)-->LVS-->Apache

獲得的值為:

x-forwarded-for:180.70.92.43   (即真實ip)

Proxy-Client-IP:null

WL-Proxy-Client-IP:null 

getRemoteAddr:123.126.50.185  (即搜狗代理ip)

 

 

×××參考資源:×××

1,http://bbs.linuxtone.org/thread-9050-1-1.html

2,http://hi.baidu.com/thinkinginlamp/item/e2cf05263eb4d18e6e2cc3e6

3,http://bbs.chinaunix.net/thread-3659453-1-1.html