从Header中X-Forwarded-For获取的IP一定是真实IP吗?

前言

在实际项目中,用户ip的获取很重要。通过报障用户的ip来快速定位用户的请求日志,还可以通过ip访问频率来进行防盗链处理。在有些项目中,比如之前我们说过的升级,通过用户ip尾号进行一部分用户的灰度升级,还比如通过ip来区分用户的地域,进行个性化的推荐等。一般获取ip的方式。都是通过Header中的X-Forward-For、X-Real-IP或Remote addr等属性获取,但是如果确保获取到的ip是真实的用户ip呢?本篇继续解密!

 

概念

  • Remote Address
    是nginx与客户端进行TCP连接过程中,获得的客户端真实地址。Remote Address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。

  • X-Real-IP
    是一个自定义头。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端。需要注意的是,X-Real-Ip 目前并不属于任何标准,代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。

  • X-Forwarded-For
    X-Forwarded-For 是一个扩展头。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。

详细分析一下,这样的结果是经过这样的流程而形成的:

  • 用户IP0---> 代理Proxy1(IP1),Proxy1记录用户IP0,并将请求转发个Proxy2时,带上一个Http Header
    X-Forwarded-For: IP0

  • Proxy2收到请求后读取到请求有 X-Forwarded-For: IP0,然后proxy2 继续把链接上来的proxy1 ip追加到 X-Forwarded-For 上面,构造出X-Forwarded-For: IP0, IP1,继续转发请求给Proxy 3

  • 同理,Proxy3 按照第二部构造出 X-Forwarded-For: IP0, IP1, IP2,转发给真正的服务器,比如NGINX,nginx收到了http请求,里面就是 X-Forwarded-For: IP0, IP1, IP2 这样的结果。所以Proxy 3 的IP3,不会出现在这里。

  • nginx 获取proxy3的IP 能通过$remote_address获取到,因为这个$remote_address就是真正建立TCP链接的IP,这个不能伪造,是直接产生链接的IP。

  1.  

很多项目通过获取 X-Forwarded-For 中首个IP作为真实IP。但是X-Forwarded-For可以伪造。本文通过以 Nginx 作反向代理时, X-Forwarded-For 及其他获取真实IP的相关内容。

  •  
请求 -> proxy1 -> proxy2 -> proxy3 -> 后端服务(/hello)

使用$remote_addr

proxy1、2、3在同一台机器(仅作测试)。使用$remote_addr,以下为proxy1、proxy2、proxy3日志格式如下:

log_format proxy1 '"[proxy1]" $remote_addr "$request" $status';log_format proxy2 '"[proxy2]" $remote_addr "$request" $status';log_format proxy3 '"[proxy3]" $remote_addr "$request" $status';

访问后,日志如下:

"[proxy1]" 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 127.0.0.1 "GET /hello HTTP/1.0" 200

结果:proxy1 拿到的是真实IP(36.157.229.110是我的IP),proxy2拿到的是proxy1的IP,proxy3 拿到的是proxy2的IP。

 

使用 X-Forwarded-For

 

在 nginx ngx_http_proxy_module的 proxy_set_header 指令中,可以通过内置变量 KaTeX parse error: Double subscript at position 12: proxy_add_x_̲forwarded_for**…remote_addr 的值追加到 X-Forwarded-For 中。若请求头中没有 X-Forwarded-For,那么 $proxy_add_x_forwarded_for 的值和 $remote_addr 相等。

 

在日志中打印出 $proxy_add_x_forwarded_for 的值。

log_format proxy1 '"[proxy1]" $remote_addr $proxy_add_x_forwarded_for "$request" $status';log_format proxy2 '"[proxy2]" $remote_addr $proxy_add_x_forwarded_for "$request" $status';log_format proxy3 '"[proxy3]" $remote_addr $proxy_add_x_forwarded_for "$request" $status';

proxy1、proxy2、proxy3 的配置中都加上:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

访问后,日志如下(文中有好几处日志,看着容易乱,尤其是第二部分$proxy_add_x_forwarded_for的值,需要通过逗号来区分):

"[proxy1]" 36.157.229.110 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 36.157.229.110, 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 127.0.0.1 36.157.229.110, 127.0.0.1, 127.0.0.1 "GET /hello HTTP/1.0" 200

结果:

proxy1中,$proxy_add_x_forwarded_for 值与 $remote_addr 相同,都是客户端的实际IP。

proxy2中,remoteaddr为proxy1的IP, remote_addr 为 proxy1的IP,remote addr为proxy1的IP,proxy_add_x_forwarded_for 中追加了 proxy1的IP,成了36.157.229.110, 127.0.0.1。

proxy3中,$proxy_add_x_forwarded_for 中继续追加了proxy2的IP,此时,X-Forwarded-For值为客户端实际IP, proxy1 IP, proxy2 IP。

因此,此时取 X-Forwarded-For 中第一个IP得到的确实为客户端真实IP。

 

伪装请求链路

还是基于上一步的配置,但客户端请求头中人为添加:X-Forwarded-For=192.168.1.1, 192.168.1.2,再看看结果:

"[proxy1]" 36.157.229.110 192.168.1.1, 192.168.1.2, 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 127.0.0.1 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1, 127.0.0.1 "GET /hello HTTP/1.0" 200

此时,$proxy_add_x_forwarded_for 的值会 基于 X-Forwarded-For 现有值 继续追加IP。因此,真实IP位于X-Forwarded-For 中哪个位置是不清楚的。

 

如何获取真实IP?

  • 使用 X-Forwarded-For + Real IP 模块

可以使用nginx的 ngx_http_realip_module 模块,从 X-Forwarded-For 或其他属性中提取真实IP。此处以 X-Forwarded-For 结合该模块为例子,需要做两件事:

一、请求途径的各代理需要设置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

二、利用 realip 模块获取真实IP

这里proxy3的部分配置(proxy3将请求直接转发到后端服务),如下:

server {  ...    location / {        set_real_ip_from 127.0.0.1;         real_ip_header    X-Forwarded-For;        real_ip_recursive on;        ...    }}

set_real_ip_from: 表示从何处获取真实IP(解决安全问题,只认可自己信赖的IP),可以是IP或子网等, 可以设置多个set_real_ip_from。

real_ip_header:表示从哪个header属性中获取真实IP。

real_ip_recursive:递归检索真实IP,若从 X-Forwarded-For 中获取,则需递归检索;若像从X-Real-IP中获取,则无需递归。

基于上一步的测试数据,试验结果:

"[proxy1]" 36.157.229.110 192.168.1.1, 192.168.1.2, 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 36.157.229.110 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1, 36.157.229.110 "GET /hello HTTP/1.0" 200

此时,proxy3 的 $remote_addr 已经拿到了客户端的真实IP 36.157.229.110,然后 proxy3 将 remote_addr 传递到后端服务中去。

 

  • 使用X-Forwarded-For + 安全设置

由于客户端可以自行传递X-Forwarded-For,因此,可以在第一个代理处重置其值,达到忽略客户端传递的X-Forwarded-For的效果。

在 proxy1 中进行如下配置:

proxy_set_header X-Forwarded-For $remote_addr;

使用 X-Real-IP

由于proxy1的 $remote_addr 是客户端真实IP,因此在 proxy1 中将X-Real-IP的值设置为 $remote_addr 即可。

proxy_set_header X-Real-IP $remote_addr;

配置下日志格式(日志中可以使用 $http_ + 自定义属性来打印其值):

log_format proxy1 '"[proxy3]" $http_x_real_ip "$request" $status';log_format proxy2 '"[proxy3]" $http_x_real_ip "$request" $status';log_format proxy3 '"[proxy3]" $http_x_real_ip "$request" $status';

结果为:

"[proxy1]" - "GET /hello HTTP/1.1" 200"[proxy2]" 36.157.229.110 "GET /hello HTTP/1.0" 200"[proxy3]" 36.157.229.110 "GET /hello HTTP/1.0" 200

proxy1 中设置了X-Real-IP的值,proxy2、proxy3日志中可以看到该值。

 

小结

实际应用中,在代理层处理好客户端真实IP,开发时直接获取即可。有些网上的例子,经常先取remoteAddr,然后取X-Real-IP,再取X-Forwarded-For,就属于代理层不做配置,把细节都丢给了后端服务来处理。

 

关注公众号:JAVA取经之旅

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页