首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  PHP

这段取真实 IP 代码有没有需要优化或改进的地方?

  •  
  •   xmlf · 52 天前 · 2447 次点击
    这是一个创建于 52 天前的主题,其中的信息可能已经有所发展或是发生改变。

    欢迎各位大佬批评指正。。谢谢

    if (isset($_SERVER)) {
     	// Use $_SERVER variables by preference
     	$HTTP_VARS = $_SERVER;
    } else if (isset($_ENV)) {
     	// Fallback to PHP environment variables
     	$HTTP_VARS = $_ENV;
    } else $HTTP_VARS = array();
    
    // Step through the captured $_SERVER or $_ENV array, ignoring the case of the keys.
    // (Some "authorities" indicate that the keys can be lower or mixed case!)
    $ipAddrList = 'unknown';
    foreach($HTTP_VARS as $key => $value) {
     	$key = strtoupper($key);
    	$value = str_replace(' ', '', $value);// Get rid of embedded blanks
    
    	if ($key == 'HTTP_FORWARDED') {
    		// We're dealing with the new HTTP Forwarded: by=identifier; for=identifier; host=host; proto=protocol
    		// See: https://tools.ietf.org/html/rfc7239
    	    $value = str_replace(',for=', ',', strtolower($value));	// Make everything lower-case and then get rid of extraneous "for=" tags
    	    $params = explode(';', $value);// Separate the Forwarded: parameters into an array
    	    foreach ($params as $key => $value) {
    		    if (substr($value,0,4) == 'for=') {
    			    $ipAddrList = substr($value,4);// Everything after "for=" is now a comma-separated list of IPv4 or IPv6 addresses
    			    break;
    		    }
    	    }
    	    break;
        }
    	if ($key == 'HTTP_X_FORWARDED_FOR') {
    	    $ipAddrList = $value;
    	    break;
        }
    	if ($key == 'HTTP_X_FORWARDED') {
    	    $ipAddrList = $value;
    	    break;
        }
    	if ($key == 'HTTP_FORWARDED_FOR') {
    	    $ipAddrList = $value;
    	    break;
        }
    	if ($key == 'HTTP_CLIENT_IP') {
    	    $ipAddrList = $value;
    	    break;
        }
    	if ($key == 'REMOTE_ADDR') {
    	    $ipAddrList = $value;
    	    break;
        }
    }
    
    $ip = preg_replace('~,.*~', '', $ipAddrList);	// Trim everything after the first comma, leaving just the first IPv4 or IPv6 address
    
    $ip = str_replace(array('"', "'"), '', $ip);	// Get rid of quotation marks used in some addresses
    if (substr($ip,0,1) == '[') {
    	$ip = preg_replace('~\]:.*~', '', $ip);	// Get rid of IPv6 port number that follows closing square bracket
    	$ip = str_replace(array('[', ']'), '', $ip);	// Get rid of square brackets enclosing IPv6 address
    } else {
    	$ip = preg_replace('~:.*~', '', $ip); // Get rid of IPv4 port number that follows last digit of address
    }
    
    unset($HTTP_VARS, $key, $value, $params, $ipAddrList); // We don't need this any more
    $_SERVER['REMOTE_ADDR'] = $ip;
    
    第 1 条附言  ·  51 天前
    目前最终方案:
    1、修改 nginx 配置文件,添加

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-FORWORD-FOR $remote_addr

    2、在我贴出的代码上面再加个判断
    if ($key == 'HTTP_X_REAL_IP') {
    $ipAddrList = $value;
    break;
    }

    目前这样应该没什么问题了。
    28 回复  |  直到 2019-01-11 10:12:17 +08:00
        1
    showecho   52 天前
    能取使用代理的访客的真实 ip ?
        2
    KomeijiSatori   52 天前
    会有 $_SERVER 函数不存在的情况吗。。
        3
    xmlf   52 天前
    @showecho 是的。或者也可以用作 使用反代后,后端取用户真实 IP。
        4
    KomeijiSatori   52 天前
    @KomeijiSatori 函数->变量
        5
    xmlf   52 天前
    @KomeijiSatori 万一呢?哈哈哈。。。
    请大佬提改进意见,谢谢。
        6
    KomeijiSatori   52 天前
    @xmlf if 改成 switch case 吧
        7
    zhujinliang   52 天前 via iPhone
    建议判断一下反代服务器的 IP 地址,以免请求方直接加 X-FORWADR 头冒充反代随意发送源 IP 地址
        8
    0312birdzhang   52 天前
    @showecho 老哥,这个能取到的话那代理的意义是什么🤔
        9
    lhx2008   52 天前 via Android
    和反代协商好头就行了,我从不用 X-FORWARD 啥的公开头,这样别人私自发 X-FORWARD 头你还得判断哪个是反代加的哪个是用户私加的
        10
    lhx2008   52 天前 via Android
    @KomeijiSatori 直接改成 list 遍历就好吧,代码是一样的
        11
    xmlf   52 天前
    @lhx2008 求大佬给段代码学习学习。
        12
    xiangyuecn   52 天前
    直接用 REMOTE_ADDR,或我们可信的请求头(存在反向代理的话)。一来就用 X_FORWARDED_FOR ? 人家都不用去用代理了,直接伪造 X_FORWARDED_FOR 就能绕过系统检测。。

    获取真实 IP 感觉是个高深的技术活,贴我一篇早年的 csdn 的帖子,https://bbs.csdn.net/topics/390727207,参考 16、21、32 楼
        13
    lhx2008   52 天前 via Android
    @xmlf
    if 自定义头存在
    return 自定义头的 IP
    else
    return 真实 IP

    还有也可以再加个反代的 IP 白名单,其他 IP 拒绝
    如果你没有能力控制反代的行为,才需要做适配性的兼容
        14
    sagaxu   52 天前 via Android
    复制粘贴一把梭
        15
    GuangXiN   52 天前 via Android
    xff 字段可以随便传
        16
    xmlf   52 天前 via Android
    @lhx2008 后端服务器直接用 iptables 设置了反代白名单。这样可否?
        17
    supervipcard   52 天前
    上高匿就玩不了了
        18
    xmlf   52 天前
    @lhx2008 如果是反代,直接在反代服务器使用
    ```
    location /{
    proxy_set_header client-real-ip $remote_addr;
    }
    ```
    可否?
    同时,在 php 程序里使用上面代码,只是修改一下顺序。
    将 if ($key == 'HTTP_CLIENT_IP') 放到最前面进行判断。
        19
    lhx2008   52 天前 via Android
    @xmlf 嗯嗯,没错,nginx 这样写会覆盖掉用户的私发头,所以服务器这边也不用验证 IP 了,是最简单的写法。验证 IP 用防火墙没问题。

    不过真实情况还可能会有多级反代,比如 nginx 外面还挂一个 CDN,这样基本上只能在 X-Forward-For 做好验证,因为这个头有标准的,一般第一个是第一级反代加的真实 IP,后面的 IP 真实性就没法保证了。

    至于 HTTP 匿名代理的 IP,我也没查有什么办法,不过现在除了爬虫,应该很少人用了,如果 ss 代理的话,不可能获取到用户真实 IP 的,只能获取到代理 IP

    当然,你这个代码还没有考虑 IPV6 的情况。
        20
    xmlf   52 天前
    @lhx2008 非常感谢大佬指点。为了给后面人更明确和清晰的答案。能否有请大佬将代码最终完善,并贴出全部代码?
    拜谢!
    另外,在我上面贴出的代码最后就是考虑了 IPV6 的。

    $ip = preg_replace('~,.*~', '', $ipAddrList); // Trim everything after the first comma, leaving just the first IPv4 or IPv6 address

    $ip = str_replace(array('"', "'"), '', $ip); // Get rid of quotation marks used in some addresses
    if (substr($ip,0,1) == '[') {
    $ip = preg_replace('~\]:.*~', '', $ip); // Get rid of IPv6 port number that follows closing square bracket
    $ip = str_replace(array('[', ']'), '', $ip); // Get rid of square brackets enclosing IPv6 address
    } else {
    $ip = preg_replace('~:.*~', '', $ip); // Get rid of IPv4 port number that follows last digit of address
    }

    unset($HTTP_VARS, $key, $value, $params, $ipAddrList); // We don't need this any more
    $_SERVER['REMOTE_ADDR'] = $ip;
        21
    lhx2008   52 天前 via Android
    X-FORWORD-FOR 我说错了,应该是最右边那个,但是用户伪造也是妥妥的没问题的,所以多级反代的时候这很难办,只能由最靠近用户的那个反代控制好
        22
    lhx2008   52 天前 via Android
    没有必要写出一个通用的代码,我 PHP 也不怎么会,简单的判断就行了。主要是根据你的程序定,比如你要像 discuz 那种开源出去可能要考虑到所有情况,自己用就随便搞下就没问题的。

    如果是要做 IP 查询业务的,比如想穿透 HTTP 代理,可能还要结合自建 dns 啥的技术,只做这些还不够
        23
    ChristopherWu   51 天前
    我做过这样的事情,可以看看: https://yonghaowu.github.io/2018/11/23/get_reql_ip/ 从限流谈到伪造 IP nginx remote_addr
        24
    xmlf   51 天前
    @ChristopherWu
    所以,目前最终方案:
    1、修改 nginx 配置文件,添加
    location /{
    proxy_set_header X-Real-IP $remote_addr;
    }
    2、在主楼我贴出的代码上面加个判断
    if ($key == 'HTTP_X_REAL_IP') {
    $ipAddrList = $value;
    break;
    }
    目前这样应该没什么问题了。
        25
    ChristopherWu   51 天前
    @xmlf 不太想看 php 的代码,有点多。我经过多方思虑,目前给出的博客应该总结的没有问题~
        26
    liuguang   51 天前
    这段代码明显有漏洞啊。我只要在 HTTP 头里面带某个假 ip 属性,你获取的就都是假的
        27
    xmlf   51 天前 via Android
    @liuguang 你说的是我 24 楼的吗?
        28
    rekulas   43 天前
    多年 php 不明白为什么获取 IP 搞这么复杂 我都是直接取 remote addr 客户端发来的任何信息都不可信
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2231 人在线   最高记录 4385   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 18ms · UTC 05:27 · PVG 13:27 · LAX 21:27 · JFK 00:27
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1