通过名字就知道,X-Forwarded-For 是一个 HTTP 扩展头部。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 ​​RFC 7239​​(Forwarded HTTP Extension)标准之中。X-Forwarded-For 请求头格式非常简单,就这样:

X-Forwarded-For: client, proxy1, proxy2

 

可以看到,XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。

如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:

X-Forwarded-For: IP0, IP1, IP2

 

Proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 Proxy2 转发请求。列表中并没有 IP3,IP3 可以在服务端通过 Remote Address 字段获得。我们知道 HTTP 连接基于 TCP 连接,HTTP 协议中没有 IP 的概念,Remote Address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP,在这个例子里就是 IP3。

Remote Address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。不同语言获取 Remote Address 的方式不一样,例如 php 是 $_SERVER["REMOTE_ADDR"],Node.js 是 req.connection.remoteAddress,但原理都一样。

问题

有了上面的背景知识,开始说问题。用 Node.js 写了个最简单的 Web Server。HTTP 协议跟语言无关,可换成任何其他语言;另外用 Nginx 也是一样的道理,如果有兴趣,可换成 Apache也一样。

1、在远程linux(10.39.16.31)运行下面代码,该程序收到 HTTP 请求后,输出一些信息:

HTTP 请求头中的 X-Forwarded-For_nginx

 

这段代码除了前面介绍过的 Remote Address 和 X-Forwarded-For,还有一个 X-Real-IP,这又是一个自定义头部字段。X-Real-IP 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端。需要注意的是:X-Real-IP 目前并不属于任何标准,代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。

2、在远程linux(10.49.16.97)上配一个 Nginx 反向代理:

 

HTTP 请求头中的 X-Forwarded-For_服务端_02

3、接下来我们做一组测试:

1)在远程window上,通过浏览器直接访问nodejs服务:

HTTP 请求头中的 X-Forwarded-For_服务端_03

 

我windows的IP是10.2.148.143,由于是直接连到Node.js 服务,Remote Address 就是我windows的 IP(用户真实IP)。同时我并未指定额外的自定义头,所以后两个字段都是 undefined。

2)在远程window上,通过nginx访问nodejs服务:

HTTP 请求头中的 X-Forwarded-For_服务端_04

 

这一次,由于是通过 Nginx 访问 Node.js 服务,所以得到的 Remote Address 实际上是 Nginx 的本地 IP(nginx和nodejs服务建立的TCP连接);此外,nginx中那两行配置也起了作用,X-Forwarded-For值是用户真实IP,x-real-ip值是windows和代理服务器nginx建立TCP连接的IP,也是用户真实IP。

3)在远程window上伪造Http头信息,通过nginx访问nodejs服务:

HTTP 请求头中的 X-Forwarded-For_node.js_05

 

这次看到,Remote Address 和x-real-ip没有变化和上面的一样,但是X-Forwarded-For最前面加了伪造的值,本来在X-Forwarded-For中最前面的IP表示用户真实IP,但是由于有了伪造信息,这个值不可靠了。

总结

1、三种IP具体含义:

  • Remote Address:他是TCP中的概念,是无法伪造的,在应用程序中获取到的Remote Address值,是直接和应用服务器建立TCP连接的IP,可能是用户真实ip(用户直接访问应用服务器时),也可能是代理服务器(通过nginx负载均衡代理时);
  • X-Real-IP:它不是标准规范,其值是与代理服务器建立TCP连接的IP,该值可能是真实ip,也可能是其他代理服务器IP;
  • X-Forwarded-For:它是http的一个标准规范,其值是X-Forwarded-For: client, proxy1, proxy2... 由于可以伪造,所以在一些安全场景下获取用户真实ip不可靠。

2、如何获取用户真实IP:

经过上面讨论其实不难得出结论,要获取用户真实IP,在安全情况下是比较难的。通常是在客户端请求时带上自己的ip。

参考:

​https://imququ.com/post/x-forwarded-for-header-in-http.html​