xmlf
V2EX  ›  PHP

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

  •  
  •   xmlf · Jan 2, 2019 · 5216 views
    This topic created in 2716 days ago, the information mentioned may be changed or developed.

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

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

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

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

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

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

    当然,你这个代码还没有考虑 IPV6 的情况。
    xmlf
        20
    xmlf  
    OP
       Jan 2, 2019
    @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;
    lhx2008
        21
    lhx2008  
       Jan 2, 2019 via Android
    X-FORWORD-FOR 我说错了,应该是最右边那个,但是用户伪造也是妥妥的没问题的,所以多级反代的时候这很难办,只能由最靠近用户的那个反代控制好
    lhx2008
        22
    lhx2008  
       Jan 2, 2019 via Android
    没有必要写出一个通用的代码,我 PHP 也不怎么会,简单的判断就行了。主要是根据你的程序定,比如你要像 discuz 那种开源出去可能要考虑到所有情况,自己用就随便搞下就没问题的。

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