获取访客 IP 的正确姿势 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gdtv
V2EX    PHP

获取访客 IP 的正确姿势

  •  
  •   gdtv 2015-10-23 09:06:57 +08:00 13483 次点击
    这是一个创建于 3648 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    $_SERVER["REMOTE_ADDR"]

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

    $user_IP = ($_SERVER["HTTP_VIA"]) ? $_SERVER["HTTP_X_FORWARDED_FOR"] : $_SERVER["REMOTE_ADDR&quo;];
    $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'] )

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

    REMOTE_ADDR 不用过滤, 这个客户端伪造不了。
    MeiganFang
        4
    MeiganFang  
       2015-10-23 09:25:36 +08:00
    REMOTE_ADDR 只能获取到代理 IP ,不能获取真实访客 IP
    Felldeadbird
        5
    Felldeadbird  
       2015-10-23 09:29:17 +08:00
    我向来都是 $_SERVER["REMOTE_ADDR"] 。一个 HTTP 请求不用浏览器的 API ,能够识别出 他的网络所有环境?我是不相信的。
    上次有一个政府的投票网站, 就是纯粹用 HTTP 请求头来判断,结果我写了一个程序,跑了 3 天,刷了几十万的票。
    Zzzzzzzzz
        6
    Zzzzzzzzz  
       2015-10-23 09:30:27 +08:00
    @MeiganFang 如果使用代理访问, 那访问服务器的真实 IP 就是该代理 IP 。 其他几个可以在 HTTP 请求头部随意伪造, 如果不是在 CDN 后端而去优先考虑那些, 反而会出问题, 等于为了防个撬门的贼直接把门拆了一样。
    laoyuan
        7
    laoyuan  
       2015-10-23 09:33:43 +08:00
    CDN 把 REMOTE_ADDR 转发成 X-Real-IP
    leakeung
        8
    leakeung  
       2015-10-23 09:41:04 +08:00
    mark
    crazystory
        9
    crazystory  
       2015-10-23 09:48:08 +08:00   1
    一、关于 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
        10
    msg7086  
       2015-10-23 09:56:01 +08:00
    nginx 可以用 real-ip 解决负载均衡代理的问题。
    其他任何时候都不应该信任传进来的 HTTP 头。

    传入的 HTTP 头属于「用户输入的数据」。
    crazystory
        11
    crazystory  
       2015-10-23 10:03:27 +08:00
    @msg7086 一样的, realip 也属于把 IP 放到一个自定义的 head 里,客户端依然可以伪造。当然如果设置了 realip 的头以后,经过负载均衡的时候负载均衡会覆盖这个伪造数据,从而使这个数据是合法的。所有的负载均衡都可以这样做,不光是 nginx 。
    MeiganFang
        12
    MeiganFang  
       2015-10-23 10:05:33 +08:00
    @Zzzzzzzzz 在复杂网络环境下,目前有精准获取访客本身 ip 的方案吗?
    crazystory
        13
    crazystory  
       2015-10-23 10:05:41 +08:00
    @msg7086 服务器对码农透明的时候,才能针对性的根据实际情况防止伪造。反之不透明的时候,理论上是没办法防止伪造的
    raysonx
        14
    raysonx  
       2015-10-23 10:10:45 +08:00 via Android
    @MeiganFang 无解。如果我用 vpn+nat , ss 或者干脆直接用 tor 访问你的网站,你是绝对不知道我的真实 ip 的。
    MrEggNoodle
        15
    MrEggNoodle  
       2015-10-23 10:13:16 +08:00
    学习了~ Thx ~
    raincious
        16
    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
        17
    Vonex  
       2015-10-23 10:40:20 +08:00   1
    正则匹配有问题
    [\d.]{7,15}
    不严谨
    [\d\.]{7,15}
    jsq2627
        18
    jsq2627  
       2015-10-23 10:54:14 +08:00 via iPhone
    客户端用代理也不是无解。上次就见到有用 webrtc 相关接口利用 STUN 服务器取真实 IP 的。
    thursday
        19
    thursday  
       2015-10-23 11:03:21 +08:00
    crazystory 说的对。楼主有些概念还不对。

    获取真实 ip 根据 服务器 环境不同,获取方式也不同。不能 一概而论。
    em2046
        20
    em2046  
       2015-10-23 11:11:30 +08:00
    @Vonex 有什么区别?
    Hello1995
        21
    Hello1995  
       2015-10-23 11:15:40 +08:00 via Android
    @em2046 . 是任意匹配字符,\. 匹配特定字符”.“。
    ango
        22
    ango  
       2015-10-23 11:31:29 +08:00
    @Vonex 在 [ ] 内具有特定匹配语义的字符时,该字符仅表示普通字符,一般不用加转义符 \ 的了,除了首位的 ^ 。
    xylophone21
        23
    xylophone21  
       2015-10-23 11:53:43 +08:00
    什么叫真实 ip ?这个定义不说清楚,讨论就没有意义了。

    我在路由器后面,我的真实 ip 是不是 192.168.1.123?还是路由器 wan 的 ip ?
    我跟 100 个人在同一个路由器后面,我们的真实 ip 都是路由器 wan 的 ip ?
    Hipponensis
        24
    Hipponensis  
       2015-10-23 12:30:12 +08:00
    mark 学习
    mornlight
        25
    mornlight  
       2015-10-23 13:20:41 +08:00
    走匿名代理的时候服务端理论上没法直接通过这几个值拿到用户真实 ip
    ryd994
        26
    ryd994  
       2015-10-23 13:43:47 +08:00 via Android
    不用 cdn ,就只用 remote address
    用 cdn 就只信任来自 cdn IP 段的 remote address
    其他一律不必考虑
    msg7086
        27
    msg7086  
       2015-10-23 13:47:59 +08:00 via Android
    @crazystory realip 可以做得比较透明,不用改后台程序,也可以过滤来源服务器。
    crazystory
        28
    crazystory  
       2015-10-23 15:15:12 +08:00
    @msg7086 但是依然脱离不了自定义 head 。。。结果并没有啥改变
    boro
        29
    boro  
       2015-10-23 16:23:15 +08:00 via iPhone
    此方法能否用来识别垃圾留言的真实 IP ?
    v7
        30
    v7  
       2015-10-23 18:01:07 +08:00   1
    http 协议代理转发原理 推荐阅读<http 权威指南>
    理论上 X-Forwarded-For 会存储途径 ip
    但并不是 X-Frowarded-For 的第一个 ip 就是真实 IP 这里是复杂的
    msg7086
        31
    msg7086  
       2015-10-23 20:28:38 +08:00
    @crazystory 你说的结果是什么东西?
    jings
        32
    jings  
       2015-10-23 23:42:15 +08:00
    <?php
    $ip = $_SERVER['REMOTE_ADDR'];
    if (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
    echo $ip;
    }
    ?>
    jings
        33
    jings  
       2015-10-24 00:22:22 +08:00   1
    上面说了那么多不如贴图
    txlty
        34
    txlty  
       2015-10-24 01:46:26 +08:00   1
    自己实现了一套相对复杂的逻辑。不是一个函数就搞定的。
    REMOTE_ADDR 、 HTTP_X_FORWARDED_FOR 都要获取并储存(其他的不用获取,纯属多余)。然后有一个不断搜集、更新的 tor 出口节点列表。这些都用来配合判断是否是正常访客。
    一旦判断为问题用户,就在数据库里打上标识。只可以正常浏览,但某些操作进行限制。

    其实还可以做的更多。比如,某些机构、学校、内网宽带用户,里面有超过一万台上网设备,却只有一个出口 IP 。这就导致 投票限制、注册限制、批量操作限制 很不好处理。
    可以搜集这些 IP ,对一些操作行为进行放宽。同时整理出学校内可信任的注册用户,加上标识。
    一旦出现恶意用户,可以及时批量处理+限制操作,同时对信任的已登录用户放行。
    有些学校、机构的出口 IP 会变化,这不要紧。获取、比对 学校内信任用户列表的最新 IP ,可以用来辅助更新对应学校的出口 IP 数据。
    。。。。。。。
    当然这些我没去做。现在逻辑已经很乱了。再去实现这些会崩溃的。
    txlty
        35
    txlty  
       2015-10-24 02:00:53 +08:00   1
    一般情况下,除了对 REMOTE_ADDR 获取到的 IP 正常的逻辑限制,还有:
    1.限制 HTTP_X_FORWARDED_FOR 有数据的请求(一般来自网上搜集的免费 http 代理)
    2.限制来自 tor 出口节点的请求。

    做到这两点就够了。 0 成本获取大量 IP 的途径就这俩。

    其他的,不管是来自 大量 web shell 、大量肉鸡、购买的匿名代理、各种 ss 、 vpn ,都是要产生经济成本的。如果有人不惜成本动用这些资源搞你,这根本限制不了。
    至于免费的公共 ss 、 vpn 、 gae 。。。 IP 数量实在有限,算不上大量 IP 。归入正常逻辑限制就行,不用特殊处理。
    evilic
        36
    evilic  
       2015-10-24 08:53:15 +08:00
    匿名投票网站不要用 FORWARDED 的 IP ,那样你会发现你的服务器会因为被刷票而卡到死的。
    wdlth
        37
    wdlth  
       2015-10-24 09:05:46 +08:00
    还有 True-CLient-IP 、 CF-Connecting-IP 等各种头……
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2771 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 31ms UTC 08:57 PVG 16:57 LAX 01:57 JFK 04:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86