72

客户端ip获取蹲坑启示: 不要侥幸

 5 years ago
source link: http://www.cnblogs.com/yougewe/p/10031995.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

怎么获取一个客户端ip ? 我想这个问题,在网上遍地都是答案!

而且多半是像下面这样:

    
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return ip;
    }

我也没说要去研究这玩意,我只是不想重复造轮子,然后努力找到了了代码仓库里有这么一段代码,所以,我就用来去获取ip了!

测试环境一切ok。然后稀拉拉地,上线了!

嘿,一上线之后,发现了数据库ip字段竟然有两个ip: 10.11.0.6, 202.116.0.83 。

好嘛,一看就知道是怎么回事了,这个用户的请求是通过代理进来的,而代理只是一种很正常的转发行为,所以必须处理这种情况!

由于原来我设置的ip字段大小为varchar(32), 所以装下这两个ip,是松的事! 大概查了下日志,并没有发现什么异常!

我想着吧,一般的用户也就是一级代理下,就差不多了。所以应该不会有什么问题,这个问题留给下个版本修复吧!

我就这么想着,玩去了。

然后,就被邮件报警了!数据库插入失败!

妈蛋,该来的始终要来!我还是太年轻了。

有一句叫: 如果你发现有个问题可能会发生,那么它就一定会发生!

不要侥幸,没人能跑得掉!

修复办法自然简单到没朋友,截取第一个',' 前的ip就行了!

    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        // 多级代理问题修复
        if (ip.indexOf(',') > 0) {
            ip = ip.substring(0, ip.indexOf(',')).trim();
        }

        return ip;
    }

好了,问题解决了,警告咱们要有敬畏之心。

下面,咱们来看看多级代理的ip是怎么回事?

在 nginx 中,咱们可以这么设置:

    location /api {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://10.11.20.22/api;
    }

这样的话,后续服务端就可以通过获取 X-Forwarded-For 来进行获取所有的代理ip地址链了!

格式为: X-Forwarded-For:10.11.247.1, 10.11.38.131, 10.11.255.1 ...

也就是说 X-Forwarded-For 是呈叠加的方式的,所以,我们应该只需要取到 第一个ip 就可以了!

事实上,X-Forwarded-For 是可以伪造的。

比如: curl -H "X-Forwarded-For:11.11.22.22" http://a.com/api

可以看到,确实很容易就进行伪造了。而按照后端代理服务器的设置,其只会往该header里添加自己的值,所以,如果此时咱们按照获取第一个值为ip,也就判断失误了。不过这种失误,一般我们还是可以接受的。不过有的场景就不适用了,比如我们通过ip来做权限管理时!所以,在做ip白名单时,还要考虑实际情况而行了!

好了,看得出通过代理标识获取ip是不可靠的,那么,有没有一种可靠的获取ip的方式呢?

其实是一个值是不可以改的:REMOTE_ADDR. 这个字段源于 tcp/udp 请求中,就会带有源地址,目标地址!

REMOTE_ADDR 是你的客户端跟你的服务器“握手”时候的IP。但是如果你使用了“匿名代理”,REMOTE_ADDR将显示代理服务器的IP。

所以,REMOTE_ADDR 虽然是不可改的,但是它却只能代表一级服务器ip,而面对现在复杂的网络环境,那是太无能为力了!

所以,没办法,还得通过约定的变量来,而这个变量又要依赖于使用的代理设置了。如上!虽然可以伪造,但是至少绝大部分是对的!

最后,给一个 tcp 发送数据样例( src -> dst):

2UFJ32V.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK