获取访客 IP 的正确姿势

2015-10-23 09:06:57 +08:00
 gdtv

1 、先看下教科书上获取 IP 的姿势:

$_SERVER["REMOTE_ADDR"]

2 、但是网上很多教程说上面的姿势不完善,还要解锁一下 36 式全方位姿势:

$user_IP = ($_SERVER["HTTP_VIA"]) ? $_SERVER["HTTP_X_FORWARDED_FOR"] : $_SERVER["REMOTE_ADDR"];
$user_IP = ($user_IP) ? $user_IP : $_SERVER["REMOTE_ADDR"];

3 、甚至还有 360 式的更全面的姿势:

function _get_client_ip() {
$clientip = '';
if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
$clientip = getenv('HTTP_CLIENT_IP');
} elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
$clientip = getenv('HTTP_X_FORWARDED_FOR');
} elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
$clientip = getenv('REMOTE_ADDR');
} elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
$clientip = $_SERVER['REMOTE_ADDR'];
}
preg_match("/[\d.]{7,15}/", $clientip, $clientipmatches);
$clientip = $clientipmatches[0] ? $clientipmatches[0] : 'unknown';
return $clientip;
}

那么究竟应该用哪种呢?我们先来看下 REMOTE_ADDR 、 HTTP_X_FORWARDED_FOR 、 HTTP_CLIENT_IP 是什么。
REMOTE_ADDR 访客 IP ,如果使用代理访问则显示代理 IP
HTTP_X_FORWARDED_FOR 访客 IP ,如果不使用代理访问则为空
HTTP_CLIENT_IP 代理服务器 IP ,如果不使用代理访问则为空
注意 REMOTE_ADDR 是无法更改的,而 HTTP_X_FORWARDED_FOR 、 HTTP_CLIENT_IP 是由客户端(一般指代理服务器)自行设定的。

那么我们应该根据不同的需求去使用上面的 IP :
一、投票系统防刷票
此时应该使用上面的方法 1 去获取客户 IP ,因为方法 2 和方法 3 获取到的 HTTP_X_FORWARDED_FOR 、 HTTP_CLIENT_IP 有可能是刷票者伪造的。
二、网站访问统计
此时应该使用方法 2 或者方法 3 获取客户 IP ,以便访客通过代理服务器访问网站时能获取到访客的真实 IP

最后要注意的是,存进数据库前别忘记过滤一下:

preg_replace( '/[^0-9a-fA-F:., ]/', '',$_SERVER['REMOTE_ADDR'] )

13021 次点击
所在节点    PHP
37 条回复
loveyu
2015-10-23 09:14:02 +08:00
还是直接用 $_SERVER["REMOTE_ADDR"] 吧,其他的各种可以通过修改 HTTP 请求头修改,伪造太简单,或者存两个
Hello1995
2015-10-23 09:14:09 +08:00
Zzzzzzzzz
2015-10-23 09:18:57 +08:00
第一个就好了, 如果用 CDN 的话用第二第三个, 不过得过滤或者限制下前端 IP 。

REMOTE_ADDR 不用过滤, 这个客户端伪造不了。
MeiganFang
2015-10-23 09:25:36 +08:00
REMOTE_ADDR 只能获取到代理 IP ,不能获取真实访客 IP
Felldeadbird
2015-10-23 09:29:17 +08:00
我向来都是 $_SERVER["REMOTE_ADDR"] 。一个 HTTP 请求不用浏览器的 API ,能够识别出 他的网络所有环境?我是不相信的。
上次有一个政府的投票网站, 就是纯粹用 HTTP 请求头来判断,结果我写了一个程序,跑了 3 天,刷了几十万的票。
Zzzzzzzzz
2015-10-23 09:30:27 +08:00
@MeiganFang 如果使用代理访问, 那访问服务器的真实 IP 就是该代理 IP 。 其他几个可以在 HTTP 请求头部随意伪造, 如果不是在 CDN 后端而去优先考虑那些, 反而会出问题, 等于为了防个撬门的贼直接把门拆了一样。
laoyuan
2015-10-23 09:33:43 +08:00
CDN 把 REMOTE_ADDR 转发成 X-Real-IP
leakeung
2015-10-23 09:41:04 +08:00
mark
crazystory
2015-10-23 09:48:08 +08:00
一、关于 REMOTE_ADDR
这个变量获取到的是《直接来源》的 IP 地址,所谓《直接来源》指的是直接请求该地址的客户端 IP 。这个 IP 在单服务器的情况下,很准确的是客户端 IP ,无法伪造。当然并不是所有的程序都一定是单服务器,比如在采用负载均衡的情况(比如采用 haproxy 或者 nginx 进行负载均衡),这个 IP 就是转发机器的 IP ,因为过程是客户端->负载均衡->服务端。是由负载均衡直接访问的服务端而不是客户端。

二、关于 HTTP_X_FORWARDED_FOR 和 HTTP_CLIENT_IP
基于《一》,在负载均衡的情况下直接使用 REMOTE_ADDR 是无法获取客户端 IP 的,这就是一个问题,必须解决。于是就衍生出了负载均衡端将客户端 IP 加入到 HEAD 中发送给服务端,让服务端可以获取到客户端的真实 IP 。当然也就产生了各位所说的伪造,毕竟 HEAD 除了协议里固定的那几个数据,其他数据都是可自定义的。

三、为何网上找到获取客户端 IP 的代码都要依次获取 HTTP_CLIENT_IP 、 HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR
基于《一》和《二》以及对程序通用性的考虑,所以才这样做。 假设你在程序里直接写死了 REMOTE_ADDR ,有一天你们的程序需要做负载均衡了,那么你有得改了。当然如果你想这么做也行,看个人爱好和应用场景。也可以封装一个只有 REMOTE_ADDR 的方法,等到需要的时候改这一个方法就行了。
msg7086
2015-10-23 09:56:01 +08:00
nginx 可以用 real-ip 解决负载均衡代理的问题。
其他任何时候都不应该信任传进来的 HTTP 头。

传入的 HTTP 头属于「用户输入的数据」。
crazystory
2015-10-23 10:03:27 +08:00
@msg7086 一样的, realip 也属于把 IP 放到一个自定义的 head 里,客户端依然可以伪造。当然如果设置了 realip 的头以后,经过负载均衡的时候负载均衡会覆盖这个伪造数据,从而使这个数据是合法的。所有的负载均衡都可以这样做,不光是 nginx 。
MeiganFang
2015-10-23 10:05:33 +08:00
@Zzzzzzzzz 在复杂网络环境下,目前有精准获取访客本身 ip 的方案吗?
crazystory
2015-10-23 10:05:41 +08:00
@msg7086 服务器对码农透明的时候,才能针对性的根据实际情况防止伪造。反之不透明的时候,理论上是没办法防止伪造的
raysonx
2015-10-23 10:10:45 +08:00
@MeiganFang 无解。如果我用 vpn+nat , ss 或者干脆直接用 tor 访问你的网站,你是绝对不知道我的真实 ip 的。
MrEggNoodle
2015-10-23 10:13:16 +08:00
学习了~ Thx ~
raincious
2015-10-23 10:25:46 +08:00
楼主写了这么多,其实是错的 :D

你根本就是没有办法如此简单的得到真实 IP 地址的,干脆承认了吧,不要再用神技了。原因就是楼上说的。而且,如果这么简单就能得到用户的 IP 地址,那么那些代理服务器之类不得都是摆设了么?

`REMOTE_ADDR`是唯一正确的 IP 地址,你只能相信这个,其次就是 CDN 给你特制的一个头——这需要你在服务器上阻止除 CDN 服务器之外的任何来源,然后信任这个头。

或者你去维护一张信任列表,将 CDN 服务器加进信任,然后看`REMOTE_ADDR`是不是 CDN 的。如果是,再去使用这几个 Proxy 相关的头,这样可以最大程度避免伪造的问题。当然,你还得搞清楚 CDN 最先发给你的是`HTTP_X_FORWARDED_FOR`、`HTTP_X_FORWARDED`还是`HTTP_CLIENT_IP`等等。
Vonex
2015-10-23 10:40:20 +08:00
正则匹配有问题
[\d.]{7,15}
不严谨
[\d\.]{7,15}
jsq2627
2015-10-23 10:54:14 +08:00
客户端用代理也不是无解。上次就见到有用 webrtc 相关接口利用 STUN 服务器取真实 IP 的。
thursday
2015-10-23 11:03:21 +08:00
crazystory 说的对。楼主有些概念还不对。

获取真实 ip 根据 服务器 环境不同,获取方式也不同。不能 一概而论。
em2046
2015-10-23 11:11:30 +08:00
@Vonex 有什么区别?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/230367

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX