服务器, 运营维护

多重代理时如何防止伪造X-Forwarded-For且获取真实IP

127.0.0.1

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

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

127.0.0.1

 

 

其实根据实际使用情况判断是否需要获取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:

Varnish:

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

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

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

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

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

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

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

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

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

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

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

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

(91 Posts)

自信、努力、活出精彩;以前未所见的颜色,绘大千世界!

View all author’s posts

22 thoughts on “多重代理时如何防止伪造X-Forwarded-For且获取真实IP”

      1. 我用上了Varnish,按您这篇文章的方法设置好了,现在后端Apache可以获取真实IP,但是Nginx的access.log里面的IP都是CDN的IP?(我已经把CDN的IP都加入了set_real_ip_from),另外前两天装了个lighttpd,按您这篇文章与网上的方法设置,结果lighttpd的access.log居然是127.0.0.1……

        1. 你set_real_ip_from没设置对而已,既然Nginx在Varnish后面,那set_real_ip_from的IP就应该是Varnish的IP了,写CDN服务器的IP干什么?
          文章的思路没错,但是如何设置好,设置对,就看你对本文的理解了。

          1. 我刚刚看了一下,set_real_ip_from varnish的IP,如果同一个服务器,就是127.0.0.1;real_ip_header 存放真实IP的变量,一般是X-Forwarded-For;这部分没有写,但是我改好了重启nginx,问题依旧!lighttpd获取的仍然是127.0.0.1

      2. 刚回复完就想到了,把log_format的$remote_addr换成$http_x_forwarded_for就可以了
        另外补充下:lighttpd是我一个二级域名的Web Server,在后端,其他二级域名都是Apache做后端。

Leave a reply