WHMCS(PHP程序)防止IP欺骗(伪造X-Forwarded-For)

前面发布了一篇文章,讲述了多重代理时防止伪造X-Forwarded-For的方法。

那只是针对使用代理服务器的情况。虽然没使用代理服务器时,很少会出现这个问题,但是也不排除有些傻逼PHP程序,如果X-Forwarded-For的内容不为空,就会主动拿来当做访客真实IP。使用CDN的话,还能获取访客的正确IP,但是没使用CDN的话……

whmcs-ip

这岂不是让那些不法分子有机可乘了吗?

大名鼎鼎的WHMCS就是其中之一!

其实如果要解决这个问题,在该PHP程序中,把类似这样的代码删掉就行:

 

但是,上面所说到的WHMCS,并非开源PHP程序,使用过ioncube加密,除非decode了,否则要修改源程序删代码,是不太可能的。

不过幸运的是,WHMCS有唯一一个没有加密且全局都使用的文件:configuration.php

既然WHMCS要把变量$HTTP_X_FORWARDED_FOR的内容拿来做访客真实IP,那么我们只需在configuration.php里面把$remote_addr(前一篇文章已解释过$remote_addr)赋值给$HTTP_X_FORWARDED_FOR。在configuration.php的<?php    ?>之间加入以下代码:

最后顺便提一下:本文仅仅是针对未使用反向代理(CDN)的网站防止伪造IP。如果你使用的是虚拟主机,且有CDN,无法修改服务器配置文件,那么只能用那“高风险”的方法去获取访客真实IP了。

 

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

当我们给网站使用例如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,只是顺应大多数人长久以来的使用习惯……

使用Varnish Cache时,让Nginx获取访客真实IP

之前发表过一篇文章:Varnish(前)+Nginx(中)时,让Apache(后)获取用户真实IP(多重代理)

该方法能成功解决使用或者不使用CDN时,在Varnish前端,Nginx中端,Apache解析PHP文件的情况下让Apache获取访客真实IP。因为当时主要是利用Nginx进行缓存,没使用其他功能,旧没让Nginx也获取访客真实的IP。

其中Varnish处理XFF的关键代码:

也就是把客户的IP赋值给XFF。那样后端处理就方便多了。

昨天在为一台Varnish+Nginx+Apache的服务器添加并测试防轻量级CC的功能,使用的Nginx模块是limit_req。虽然访问网站时,能获取访客的正常IP,但是Nginx得到的却是Varnish所在的服务器的IP。

测试时发现,由于limit_req模块是在Nginx上的,那样受到CC攻击的话,Nginx就会误判断是Varnish服务器的IP发出的请求,就会Ban掉Varnish服务器的IP,再次遇到Varnish的IP时,就会返回403……

403

 

尝试了Nginx的realip_module,发现无任何效果。

于是在另一台空闲的VPS上安装了Ubuntu 12.04,安装了Varnish,Nginx,PHP5-FPM进行测试。

研究了下Varnish和Nginx的配置,于是写了和PHP程序,查看XFF等信息:

测试后发现,REMOTE_ADDR一直都是Varnish所在的服务器的IP,而limit_req获取的正是REMOTE_ADDR的内容,那就是Ban Varnish的IP的原因了。由于Varnish那设置了HTTP_X_FORWARDED_FOR的内容舍弃原有内容(例如CDN服务器传来的),并且只含有客户IP,因此该项正常且不会影响REMOTE_ADDR。X-Real-ip这项内容是空白的,使用realip_module时,会使用到real_ip_header X-Real-IP这代码,大概是告诉服务器X-Real-IP才是客户真正的IP吧。

这下目标明确,要么搞定REMOTE_ADDR的,要么让real_ip_header X-Real-IP的real_ip_header获取真实的IP。

在Varnish那尝试了几次把客户的IP赋值给REMOTE_ADDR,使用或者不使用CDN时,REMOTE_ADDR就出问题了,要么是客户IP,要么CDN服务器的IP。

想了想,既然Nginx的real_ip_header能让Nginx知道访客真实IP,而HTTP_X_FORWARDED_FOR只有访客IP,把HTTP_X_FORWARDED_FOR的内容赋值给real_ip_header不就可以了么?

于是在删除原来所有与real_ip_header有关的代码,在Nginx的网站配置的server层(注意是server不是location)加入如下代码:

果然立竿见影,再次访问那个PHP文件,不管前面有没有上CDN,REMOTE_ADDR都与XFF的内容一模一样,也就是说,Nginx能获取到访客真实IP了。

把刚刚的成果帮到LANVMP的服务器上,在本机上狂F5,返回403,这是预料之中的事情,关键是,别的访客是不是200。于是再次打开17ce进行检查,非常好,全部都是200状态:

200

 

虽然解决该问题花了很多时间去折腾,不过能完美解决,也值得了……