服务端获取客户端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

猜你喜欢

转载自blog.csdn.net/qq_35781732/article/details/85319384