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

Saturday, October 3, 2009

Seg fault when calling php memcache::addServer

addServer('localhost', 11211);
var_dump($mc_0->get('ss'));
var_dump($mc_0->set('ss', 1, NULL, 100));
var_dump($mc_0->get('ss'));

$mc_1 = new Memcache;
$mc_1->addServer('localhost', 11211, NULL);
var_dump($mc_1->get('ss'));
var_dump($mc_1->set('ss', 2, NULL, 100));
var_dump($mc_1->get('ss'));


$mc_2 = new Memcache;
$mc_2->addServer('localhost', 11211, NULL, 1); 
var_dump($mc_2->get('ss'));
var_dump($mc_2->set('ss', 3, NULL, 100));
var_dump($mc_2->get('ss'));

$mc_4 = new Memcache;
$mc_4->addServer('localhost', 11211, NULL, 1, NULL);
var_dump($mc_4->get('ss'));
var_dump($mc_4->set('ss', 4, NULL, 100));
var_dump($mc_4->get('ss'));

$mc_5 = new Memcache;
$mc_5->addServer('localhost', 11211, NULL, 1, NULL, NULL);
var_dump($mc_5->get('ss'));
var_dump($mc_5->set('ss', 5, NULL, 100));
var_dump($mc_5->get('ss'));

$mc_6 = new Memcache;
$mc_6->addServer('localhost', 11211, NULL, 1, NULL, NULL, NULL);
var_dump($mc_6->get('ss'));
var_dump($mc_6->set('ss', 6, NULL, 100));
var_dump($mc_6->get('ss'));

$mc_7 = new Memcache;
$mc_7->addServer('localhost', 11211, NULL, 1, NULL, NULL, NULL, NULL);
var_dump($mc_7->get('ss'));
var_dump($mc_7->set('ss', 7, NULL, 100));
var_dump($mc_7->get('ss'));




Guess whatz the result :?

sskaje@newborn:~/test$ php test3.php 
string(1) "7"
bool(true)
string(1) "1"
string(1) "1"
bool(true)
string(1) "2"
string(1) "2"
bool(true)
string(1) "3"
string(1) "3"
bool(true)
string(1) "4"
string(1) "4"
bool(true)
string(1) "5"
string(1) "5"
bool(true)
string(1) "6"
string(1) "6"
bool(true)
string(1) "7"
Segmentation fault
sskaje@newborn:~/test$ 

Noticed ? Thatz a seg fault at the very end. Now look above to see where error occurs.
Hmm...
addServer('localhost', 11211, NULL, 1, NULL, NULL, NULL, NULL);

Yup, this is the line where that seg fault occurs.
Compare this line with the $mc_2 lines. Yes, you saw the difference is the forth argument when calling addServer. The $mc_2 uses 1 and $mc_7 uses NULL.

I'll check the source out to find what exactly causes this error when I'm really that free.