当我们给网站使用例如CDN,Nginx或Varnish等缓存服务时,为了获取访客的真实IP,大多数会地把访客的真实IP赋值给X-Forwarded-For(下文简称XFF)。
但是因为XFF是个HTTP请求头,也就是最前面带有http_,因此这类http信息就可以被伪造。
其实根据实际使用情况判断是否需要获取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:
1 |
proxy_set_header X-Forwarded-For $remote_addr; |
Varnish:
1 |
set req.http.X-Forwarded-For = client.ip; |
另外,使用Varnish的朋友也需注意下默认的XFF处理方式:
1 2 3 4 5 6 7 8 |
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传给后端:
1 2 3 |
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的关键:
1 |
proxy_set_header X-Forwarded-For $remote_addr; |
Varnish处,获取从CDN节点传来的XFF内容,赋值给XFF,传给后面的Nginx:
1 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
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的变量:
1 2 3 4 |
RPAFenable On RPAFsethostname On RPAFproxy_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,只是顺应大多数人长久以来的使用习惯……
还是不懂呀
如果是CDN+前端Nginx反向代理+后端Apache呢?没有使用Varnish
前端Nginx使用REAL IP模块,获取CDN所有的ID,写到set_real_ip_from里面,一行一个,支持CIDR。然后用proxy模块的proxy_set_header发送HTTP头给APACHE,APACHE用rpaf模块。
我用上了Varnish,按您这篇文章的方法设置好了,现在后端Apache可以获取真实IP,但是Nginx的access.log里面的IP都是CDN的IP?(我已经把CDN的IP都加入了set_real_ip_from),另外前两天装了个lighttpd,按您这篇文章与网上的方法设置,结果lighttpd的access.log居然是127.0.0.1……
你set_real_ip_from没设置对而已,既然Nginx在Varnish后面,那set_real_ip_from的IP就应该是Varnish的IP了,写CDN服务器的IP干什么?
文章的思路没错,但是如何设置好,设置对,就看你对本文的理解了。
我刚刚看了一下,set_real_ip_from varnish的IP,如果同一个服务器,就是127.0.0.1;real_ip_header 存放真实IP的变量,一般是X-Forwarded-For;这部分没有写,但是我改好了重启nginx,问题依旧!lighttpd获取的仍然是127.0.0.1
lighttpd在Nginx后面,那么请问你给lighttpd弄了获取真实IP的模块了吗?
完全按照网上的方法做了,你可以去搜索一下”lighttpd获取真实IP”
Varnish的backened,set header,nginx的real IP和proxy set header贴出来。
刚回复完就想到了,把log_format的$remote_addr换成$http_x_forwarded_for就可以了
另外补充下:lighttpd是我一个二级域名的Web Server,在后端,其他二级域名都是Apache做后端。
如果有做CDN,那么还能不能改为$remote_addr?帮朋友做的一个网购站,用的xff判断,有时候会出问题的
应该只允许通过cdn访问,获取$http_x_forwarded_for作为访客真实IP。
变量改为$http_x_forwarded_for?这样还能XFF伪造?
只允许通过cdn服务器访问就不会被欺骗了。
这都能仿造。。。
全改成纯技术代码的文章了,表示大多看不懂。
另外,请麻烦帮我的连接更改下,加个www.谢谢!
自己懂什么写什么了,记录下来,方便别人也方便自己。
博主是最运维的高手?~~
还不算吧,只是把我所知道分享分享。
d 39 有啥用处
d8 小白一枚。。。代码什么掐死算了