接到一个投票活动的需求,需要做 IP 限制,每个 IP 限制一定的投票机会。我在搜索引擎上搜索了关键词: PHP 客户端 IP ,结果基本上都是以下内容:
if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) { $onlineip = getenv('HTTP_CLIENT_IP'); } elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) { $onlineip = getenv('HTTP_X_FORWARDED_FOR'); } elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) { $onlineip = getenv('REMOTE_ADDR'); } elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) { $onlineip = $_SERVER['REMOTE_ADDR']; }
这段代码在使用广泛的 《 discuz 》论坛软件,以及众多开放源代码的 PHP 软件里都会有使用到,大致思路是获取最终 的客户端 IP 地址(能获得使用代理访问的用户的 IP 地址)。
由于很多成熟的程序都使用了这段代码获取客户端 IP 地址,所以我也就放心的运用在了程序里,好在后来有同事提醒,发现这段代码不能用在限制 IP 的投票程序里,因为 HTTP_X_FORWARDED_FOR 这个是可以伪造的,只要在请求头里增加 X-Forwarded-For 。在服务器端的 $_SERVER[‘HTTP_X_FORWARDED_FOR’] 接收到的就是这个请求头的内容。
下面我用程序说明一下:
http://localhost/i.php 内容是通过上面的代码获取 IP 地址,并打印出来。
编写构造请求的代吗,请求这个 URL :其中在请求头里增加了 X-Forwarded-For 这个参数
$head = array(); $head[] = 'GET /i.php HTTP/1.1'; $head[] = 'Host: localhost'; $head[] = 'X-Forwarded-For: 255.255.255.255' ; $head[] = 'Connection: Close' ; $head = join("\r\n",$head) ; $head .= "\r\n\r\n"; $fp = fsockopen('localhost', 80); fwrite($fp, $head); $response = array() ; while($buff = fread($fp, 4096)){ $response[] = $buff; } print join('',$response) ;
执行这段代码,可以得知,服务器端 (localhost/i.php) 打印了 255.255.255.255 。
说明了这种获取客户端 IP 的方法在限制 IP 的投票活动里是不可取的,客户端的 IP 地址可用伪造。而直接使用 $_SERVER['REMOTE_ADDR'] 虽然获取到的不是用户的最终 IP 地址,但是限制的功能是直接有效的达到了。
当然,也不能说那段代码是错误的。在一些对 IP 不做限制的需求里,应该使用,比如,在一些有很多地域性子网站的网站,通过用户访问的 IP ,直接跳转到该用户所在区域的子网站等。