Friday, October 16, 2009

完美实现绕过特定的IP限制

由于http proxy的存在,PHP中取IP不能直接使用 REMOTE_ADDR 字段获取。一般的方法可能是先判断 HTTP_X_FORWARDED_FOR 等字段存不存在,最后再选择取用 REMOTE_ADDR。于是,这样一来,取出的IP可能就是 202.202.202.202, 10.1.10.5, 10.1.10.253 ..... 也许你在联盟(Bitunion) 的IP显示上曾经见到过这样的信息,尤其是当你在用手机GPRS上网...

好吧,于是问题出现了。
正是由于代理的出现,这样格式的IP也就成了一个问题。
为什么说是问题呢?想想常见的限制IP的操作,常见的有几种方式,一种直接IP字符串前几位比较,一种是用IPv4的按位与或计算。对于前者而言,实在无法控制客户端从他所在的网络出来的第一部分的IP会是什么;对于后者,切分IP之后选择哪个部分就成了该考虑问题。
即便不说IP限制,检查IP格式也很麻烦了。

虽然联盟的代码并不涉及到IP限制的问题(其实也是有的,只是一般人不见得能发现,LOL),但是早先的Session表,IP字段只有15个字节长度,完全是以 xxx.xxx.xxx.xxx 作为标准考量的,正因为这个原因,早先看到的用户发帖IP可能是 xxx.xx.xx.xx, 1 这样很诡异的格式。于是我把所有的IP字段都扩充到50字节,基本没见过有四重的情况。

好吧,回到正题。
目前某个PHP系统的IP获取策略是,取REMOTE_ADDR 的直接IP和HTTP_X_FORWARDED_FOR 等代理IP,如果有代理IP,则切分并取出最前边的一个,否则就直接用REMOTE_ADDR的数据。

于是问题出现了。
这个系统的部分PHP脚本做了IP段限制,例如 10.1.1* 。
解读一下:
如果我没走任何代理,直接访问服务器,且我的IP如 10.1.1.* 10.1.1?.* 10.1.1??.* 那就认为我的IP段合法;
加入我走了代理,访问服务器,只要我在代理前的IP是上边的几个,那就行了;
……

说了这些,能明白我的思路了么?

好吧,实验开始了。
首先直接访问 http://t.sskaje.name/ip.php
返回的结果是
string(0) "" string(12) "67.205.42.90" 

说明一下,第一个dump出来的是所谓的代理IP,第二个是按照上边的策略取出的IP。

然后第一个实验。

开始前,先说明一下实验环境:测试机一台,我的笔记本,没必要说配置,浏览器无关,内网IP,10.210.18.*** ;VMWare虚拟机一台,Ubuntu Server 8.04.3, NAT模式和笔记本构建了一个 192.168.175/24 的子网; 虚拟机配了nginx,因为是用来做budev开发的,所以配置基本按253的走的。虚拟机的域名是 proxy.t.sskaje.name IP为 192.168.175.128;本机NAT网络下的IP为 192.168.175.2。http://t.sskaje.name/,这是一个在dreamhost上的虚拟主机,感谢Loster的支持。另外,刚才那个ip.php的脚本是从这里提到的某系统直接拿着增加了代理IP的独立输出。


第一个实验是配好nginx转发。
我的转发的目的是把所有的发往我的虚拟机的php解析请求,转发给 t.sskaje.name,也就是proxy.t.sskaje.name -> t.sskaje.name。

location 的一个directive的配置如下:


location ~ /.*\.php {
proxy_pass http://t.sskaje.name;
proxy_redirect off;
proxy_set_header Host 't.sskaje.name';
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_read_timeout 90;
}


这几乎可以说是最简单的nginx proxy配置了。

访问 http://proxy.t.sskaje.name/ip.php 的输出为
string(13) "192.168.175.2" string(13) "192.168.175.2" 

也就是说代理前的IP是 192.168.175.2,按策略取出的IP也是代理的IP,没问题。因为我的NAT网络里,我的IP是192.168.175.2。

下一个测试是,改IP。
改的是啥呢?
代理字段,当然,X-Forwarded-For这里了。
好,现在的配置文件改成了

location ~ /.*\.php {
proxy_pass http://t.sskaje.name;
proxy_redirect off;
proxy_set_header Host 't.sskaje.name';
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For '10.20.30.40';

proxy_read_timeout 90;
}

再次访问:http://proxy.t.sskaje.name/ip.php
string(11) "10.20.30.40" string(11) "10.20.30.40" 

Good job!

继续,下一个测试,改个IP头试试。
上述系统的内外IP限制了 10.xx.+ 的某个段,于是我把代理X-Forwarded-For的数据内容指定成这样一个段的IP。
其实就是某台仿真机的IP。
然后把proxy_pass字段改成内网接口的IP,还得把proxy_set_header的host部分改一下。
嗯,改好了

这个时候我的配置是

location ~ /.*\.php {
proxy_pass http://202.***.**.180;
proxy_redirect off;
proxy_set_header Host '***.sina.com.cn';
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For '10.**.**.240';

proxy_read_timeout 90;
}


访问 http://proxy.t.sskaje.name/***/***/***/add*******.php 。
返回值是:
{"errno":-4,"errmsg":"uid\u4e0d\u80fd\u4e3a\u7a7a"}



如果直接访问那个接口呢?
试试:http://***.sina.com.cn/***/***/***/add*******.php
{"errno":-1,"errmsg":"Ip\u53d7\u9650"}



JSON的编码自己解吧。
明眼人应该看出来区别了吧。

# EOF

No comments:

Post a Comment