当我们给网站使用例如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:
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,只是顺应大多数人长久以来的使用习惯……
转载于:https://blog.51cto.com/tianshili/1638551