当我们给网站使用例如CDN,Nginx或Varnish等缓存服务时,为了获取访客的真实IP,大多数会地把访客的真实IP赋值给X-Forwarded-For(下文简称XFF)。

 

但是因为XFF是个HTTP请求头,也就是最前面带有http_,因此这类http信息就可以被伪造。

 

d043ad4bd11373f048ea7b82a70f4bfbfaed04d1

 

其实根据实际使用情况判断是否需要获取XFF内容就不会出现这些问题。

 

拿Nginx的反代理(Proxy模块)功能来说,有人会把$proxy_add_x_forwarded_for变量的内容传给后端作为用户的真实IP。

 

Nginx对该变量的处理非常智能,当有XFF传过来时,Nginx就会自动把Nginx服务器的IP加到原来的XFF最后面,再发给后端。

 

这智能也带来了问题,如果访客自己伪造了一个XFF变量内容,那样后端服务器所获取的访客IP也是假的,给不怀好意的人有机可乘……

 

对于最前端来说,访客IP的变量只有一个是真实且无法伪造的——$remote_addr,此变量最前面不带有http_。

 

对于使用了Nginx,Varnish之类的做前端,把$remote_addr(Varnish的变量名为client.ip)作为访客IP是最明智的选择。

 

Nginx:

 

proxy_set_header X-Forwarded-For $remote_addr;

 proxy_set_header X-Forwarded-For $remote_addr; 

 

Varnish:

 

set req.http.X-Forwarded-For = client.ip;

  set req.http.X-Forwarded-For = client.ip; 

 

另外,使用Varnish的朋友也需注意下默认的XFF处理方式:

 

     if (req.restarts == 0) {

        if (req.http.x-forwarded-for) {

           set req.http.X-Forwarded-For =

               req.http.X-Forwarded-For + ", " + client.ip;

        } else {

            set req.http.X-Forwarded-For = client.ip;

        }

     }

    if (req.restarts == 0) {        if (req.http.x-forwarded-for) {           set req.http.X-Forwarded-For =               req.http.X-Forwarded-For + ", " + client.ip;        } else {            set req.http.X-Forwarded-For = client.ip;        }     } 

 

可以看出,Varnish的默认对XFF处理方式和Nginx Proxy模块的$proxy_add_x_forwarded_for基本一样。也一样存在XFF欺骗的问题。

 

如果没有使用CDN,个人建议把判断XFF是否有内容的代码去掉,直接获取remote_addr传给后端:

 

     if (req.restarts == 0) {

            set req.http.X-Forwarded-For = client.ip;

     }

       if (req.restarts == 0) {            set req.http.X-Forwarded-For = client.ip;     } 

 

当然,前面所提及的只是最简单的情况。

 

假设有一个网站使用这样的环境:PHP解 析引擎为Apache,Apache前面有一个Nginx做缓存,Nginx前面还有一个Varnish做缓存,Varnish前面又加一个内容分发网络 (CDN,所有节点均使用Nginx),我如何实现在Apache处能获取到访客真实IP,且不会出现XFF欺骗的情况?

 

首先,要求后端服务器,均不能直接访问,只允许通过CDN服务器访问!否则可能出现欺骗XFF的情况。

 

最前端——CDN节点的Nginx以$remote_addr变量作为访客的真实IP!这是保证不被欺骗XFF的关键:

 

proxy_set_header X-Forwarded-For $remote_addr;

 proxy_set_header X-Forwarded-For $remote_addr; 

 

Varnish处,获取从CDN节点传来的XFF内容,赋值给XFF,传给后面的Nginx:

 

set req.http.X-Forwarded-For = req.http.X-Forwarded-For;

  set req.http.X-Forwarded-For = req.http.X-Forwarded-For; 

 

Nginx 处,如果要使用limit_req模块,或记录日志等功能,需要realip模块,设置真实IP来源于Varnish服务器的IP,并告诉Real模块, 哪个变量储存着访客的真实IP,然后把Varnish传过来带有真实IP的XFF赋值给XFF传给Apache:

 

server {

  ......

  set_real_ip_from   varnish的IP,如果同一个服务器,就是127.0.0.1;

  real_ip_header     存放真实IP的变量,一般是X-Forwarded-For;

  ......

 

  location / {

    ......

    proxy_set_header X-Forwarded-For $http_x_forwarded_for;

    ......

  }

}

  server {  ......  set_real_ip_from   varnish的IP,如果同一个服务器,就是127.0.0.1;  real_ip_header     存放真实IP的变量,一般是X-Forwarded-For;  ......   location / {    ......    proxy_set_header X-Forwarded-For $http_x_forwarded_for;    ......  }} 

 

Apache处,需要安装rpaf模块,并告诉rpaf模块,安装varnish,nginx的服务器IP和储存访客真实IP的变量:

 

RPAFenable On

RPAFsethostname On

RPAFproxy_ips Varnish服务器的IP Nginx服务器的IP       #不同IP之间用空格隔开

RPAFheader X-Forwarded-for

 RPAFenable OnRPAFsethostname OnRPAFproxy_ips Varnish服务器的IP Nginx服务器的IP       #不同IP之间用空格隔开RPAFheader X-Forwarded-for 

 

可 见,从最前端的CDN节点到最后端的Apache,变量XFF的一直没变,一直都是只有访客的真实IP,如果Nginx处只是缓存,甚至连realip模 块都不需要,直接就把XFF传送给Apache,这极大的简化了后端的处理,不仅能获取到访客的真实IP地址,也不会出现伪造XFF的问题。

 

最后我顺便提一下,X-Forwarded-For只是个变量名,你完全可以改成其它你喜欢的名字,我全文都用了X-Forwarded-For,只是顺应大多数人长久以来的使用习惯……