【干货分享】获取用户IP的正确姿势
如何获取用户的IP,这个需求简直是太常见了,像登录入口,注册入口,投票,日志记录,api接口中判断同一个ip单位时间内的请求数,可是怎么去获取用户的真实IP呢?网上的代码很多,好多人直接拿来就用,却没有想到带来了很大的安全问题。1 代码示例
1 2 3 4 5 6 7 8 9 10 | <?php if(!empty($_SERVER['HTTP_CLIENT_IP'])){ $myip = $_SERVER['HTTP_CLIENT_IP']; }else if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){ $myip = $_SERVER['HTTP_X_FORWARDED_FOR']; }else{ $myip= $_SERVER['REMOTE_ADDR']; } echo $myip; ?> |
这是网上的一个示范例子,我们很多同事也这么写,上面这个例子是php实现的,由于HTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR,HTTP_X_FORWARDED,HTTP_X_CLUSTER_CLIENT_IP,HTTP_FORWARDED_FOR,HTTP_FORWARDED,HTTP_VIA (经过的 Proxy)这些以HTTP打头的server变量都是用户可控的,由此可导致xss,认证绕过等缺陷。
下面我们看下python的例子:
1 2 3 4 5 6 7 8 | def get_ip(request): try: return request.META['HTTP_X_FORWARDED_FOR'] except KeyError: try: return request.META['HTTP_X_REAL_IP'] except KeyError: return request.META.get('REMOTE_ADDR', None) |
也是由于客户端变量可控导致获取的ip可为任意值。在此例中,X-Real-IP是nginx特有的,通过配置proxy_set_header X-Real-IP $remote_addr;从REMOTE_ADDR中取值。
2 X-Forwarded-For和 REMOTE_ADDR的区别
REMOTE_ADDR代表着客户端的IP,但是这个客户端是相对服务器而言的,也就是实际上与服务器相连的机器的IP(建立tcp连接的那个),这个值是不可伪造的,如果没有代理的话,这个值就是用户实际的IP值,有代理的话,用户的请求会经过代理再到服务器,这个时候REMOTE_ADDR会被设置为代理机器的IP值。
正如前面所说,有了代理就获取不了用户的真实IP,由此X-Forwarded-For应运而生,它是一个非正式协议,在请求转发到代理的时候代理会添加一个X-Forwarded-For头,将连接它的客户端IP(也就是你的上网机器IP)加到这个头信息里,这样末端的服务器就能获取真正上网的人的IP了。
假设用户的请求顺序如下:
网民电脑ip->代理服务器1–>代理服务器2–>目标服务器
REMOTE_ADDR:代理服务器2的IP值
X-Forwarded-For就是:网民电脑IP,代理1的IP,代理2的IP
在这里只有REMOTE_ADDR是可信的,其他从客户端获取的数据都是不可信的,都是可伪造的。下面简单示例下一个篡改X-Forwarded-For的情况:
1 2 3 4 5 6 7 8 9 10 | GET / HTTP/1.1 Host: www.myip.cn Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) *** WebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Encoding: gzip, deflate, sdch Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2 Cookie: Hm_lvt_380ffd3c2225d34ca2087c6970395366=1473755162; Hm_lpvt_380ffd3c2225d34ca2087c6970395366=1473755299; sc_is_visitor_unique=rx4067297.1473755300.43C8C2ACB3CA4FAAEB8885235516D36A.1.1.1.1.1.1.1.1.1 X-Forwarded-For: 127.0.0.111111 |
返回信息是:
1 | <font style="font-family:Arial,Helvetica,Sans Serif;font-size: 24pt;" color="#0066CC"><b>您的IP地址: 127.0.0.111111</b></font> |
3 正确的代码示例
在X-Forwarded-For信息头中可以提取真实的用户IP,但是这个IP是可以伪造的,如果从X-Forwarded-For提取IP作为用户的IP对于存在登录次数,api速率限制等一些接口是致命的缺陷,因为任意构造出无数的合法或者非法IP地址。而REMOTE_ADDR只是服务器前端的IP地址,如果没有代理就是用户的真实地址。这个是不可伪造的,而且代理是有限的,可以基于此来获取IP。在wordpress中,获取客户的IP地址代码如下:
1 | $remote_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ); |
如果是python代码的话:
remote_ip = request.META.get(‘REMOTE_ADDR’, None)
当然上述代码也存在缺陷,就是服务器端开了nginx反向代理的时候,每次获取的都是反向代理的IP,这不是我们的预期,需要nginx在配置反向代理的时候做一定设置并且修改代码。如:
1 2 3 | proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
或者采用realip模块,配置如下:
1 2 | set_real_ip_from 10.1.10.0/24; real_ip_header X-Forwarded-For; |
在存在反向代理的情况下,如果直接获取REMOTE_ADDR,得到的是反向代理IP的值,从上面的配置也可以看出,在反向代理nginx的配置中将REMOTE_ADDR赋给了X-Real-IP,那么也是从X-Real-IP中来获取用户的IP,如下才是正确的获取用户IP的方式:
1 2 3 4 5 | def get_ip(request): try: return request.META['HTTP_X_REAL_IP'] except KeyError: return request.META.get('REMOTE_ADDR',None) |
4 总结
X-Forwarded-For可被用户伪造,不应该被信任;REMOTE_ADDR是使用“REMOTE_ADDR”机器的前一个建立tcp连接的机器的地址,是不可伪造的,在无代理时可以理解为用户的IP地址,有反向代理时,先将REMOTE_ADDR赋给X-Real-IP,最后可以从X-Real-IP中获取用户的IP。
参考文献:
http://gong1208.iteye.com/blog/1559835
http://devco.re/blog/2014/06/19/client-ip-detection/
http://blog.pengqi.me/2013/04/20/remote-addr-and-x-forwarded-for/
文章分类: 安全分享
- 点赞
- 收藏
- 关注作者
评论(0)