你获取的真的是用户真实的IP吗

1,576 阅读4分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

网上的获取方法

在获取用户真实IP的时候,在网上搜一下,网上有很多关于PHP获取用户真实IP的方法,例如:

public static function getClientIp()
{
    $ip = '';
    if ($_SERVER('HTTP_CLIENT_IP')) {
        $ip = $_SERVER('HTTP_CLIENT_IP');
    }
    if ($_SERVER('HTTP_X_REAL_IP')) {
        $ip = $_SERVER('HTTP_X_REAL_IP');
    } elseif ($_SERVER('HTTP_X_FORWARDED_FOR')) {
        $ip = $_SERVER('HTTP_X_FORWARDED_FOR');
        $ips = explode(',', $ip);
        $ip = $ips[0];
    } elseif ($_SERVER('REMOTE_ADDR')) {
        $ip = $_SERVER('REMOTE_ADDR');
    }

    return $ip;
}

看了许多文章基本都是这么写的,但这样获取真的正确吗?

分析参数

上面方法出现了四个参数:HTTP_CLIENT_IP、HTTP_X_REAL_IP、HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR。

现在我们就逐个分析每个参数是干什么的。

1、HTTP_CLIENT_IP和HTTP_X_REAL_IP,都是一个自定义头,目前并不属于任何标准,是代理服务器和 Web服务器之间可以约定用任何自定义头来传递这个信息,可以是这两个,也可以随意写成其他的。

2、HTTP_X_FORWARDED_FOR是一个扩展头。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP,现在已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用

3、REMOTE_ADDR 是最后一个与服务器握手的IP,是不能伪造的,可能是用户真实IP,可能是用户的代理服务器ip,也可能是自己的反向代理服务器的IP. 主要有以下几种情况:

1)最简单的架构,用户不使用代理服务器,我们也没加代理服务器,这种情况 REMOTE_ADDR就是用户的真实IP:127.168.1.1

image.png

2) 用户使用代理服务器,REMOTE_ADDR最终的结果就是用户代理服务器的IP:132.128.1.3

image.png

3)用户不使用代理服务器,我们添加的有代理服务器,REMOTE_ADDR最终的结果就是我们的代理服务器的IP:192.168.1.4

image.png

4) 用户使用代理服务,我们也添加的有代理服务器,REMOTE_ADDR最终的结果是我们的代理服务器的IP:192.168.1.4

image.png

这几个参数怎么来的

1、REMOTE_ADDR是最后一次与服务器通信的ip,是不能伪造了,但不一定是用户的真实IP。
2、HTTP_CLIENT_IP,HTTP_X_REAL_IP,HTTP_X_FORWARDED_FOR属于http请求头信息,主要有两个来源:
第一:用户直接伪造,例如使用postman,在header里直接添加 image.png 得到的结果

array (
  'HTTP_HOST' => '192.168.197.128',
  'HTTP_X_FORWARDED_FOR' => '192.168.0.4,192.168.0.5',
  'HTTP_X_REAL_IP' => '192.168.0.3',
  'HTTP_CLIENT_IP' => '192.168.0.2',
  'REDIRECT_STATUS' => '200',
  'SERVER_NAME' => '_',
  'SERVER_PORT' => '80',
  'SERVER_ADDR' => '192.168.197.128',
  'REMOTE_PORT' => '64746',
  'REMOTE_ADDR' => '192.168.197.1',
  'SERVER_SOFTWARE' => 'nginx/1.14.0',
  ......
  下面省略
)

第二:来自代理服务器的配置,例如这种架构

image.png nginx负载均衡服务器的配置如下:

upstream upstream_name {
    server 192.168.0.2;
    server 192.168.0.3;
}
server {
    listen 80;
    server_name _;
    location / {
        proxy_pass http://upstream_name;
        proxy_pass_header Host $host;
        proxy_pass_header X-Real-ip $remote_addr;
        proxy_pass_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
    }
}

怎么获取用户真实IP

首先排除用户使用高匿名代理这种情况,因为使用了高匿名代理,我们是获取不到用户真实IP的,如果哪位大神能够在这种情况获取到,还请不吝赐教。

获取用户真实的IP,我们要根据自己的架构情况。

第一种:自己的服务器没有使用任何代理 image.png REMOTE_ADDR是最值得相信的,直接拿这个值就行。

第二种:我们添加了slb,做负载均衡

image.png REMOTE_ADDR就是负载均衡那台服务器的IP了,所以这个值我们不能取。上图这种架构得到的值分两种情况:

1)用户不伪造参数,我们最终得到的值为:
HTTP_X_REAL_IP为127.168.1.1,HTTP_X_FORWARDED_FOR为127.168.1.1。

2)用户伪造参数,X_REAL_IP传47.12.45.78,X_FORWARDED_FOR传47.23.56.89,得到的值为:
HTTP_X_REAL_IP为127.168.1.1,HTTP_X_FORWARDED_FOR为47.23.56.89,127.168.1.1。

结合上面这两种情况,HTTP_X_REAL_IP是用户真实ip,HTTP_X_FORWARDED_FOR取最后一个IP就行,分析到这里,再看看上面的网上的获取方法,感觉网上的方法没毛病,其实不然,我们看下面的第三种架构。

第三种:有些网站为了防止网络攻击添加了高防,就相当于又多了一层代理服务器

image.png 最终得到的结果,REMOTE_ADDR为最后一层代理服务器的ip:192.168.1.1,HTTP_X_REAL_IP和HTTP_X_FORWARDED_FOR同样分用户伪造和用户不伪造两种情况:

1)用户不伪造参数,我们最终得到的值为:
HTTP_X_REAL_IP为47.12.45.78,HTTP_X_FORWARDED_FOR为127.168.1.1,47.12.45.78。

2)用户伪造参数,X_REAL_IP传47.12.45.78,X_FORWARDED_FOR传47.23.56.89,得到的值为:
HTTP_X_REAL_IP为47.12.45.78,HTTP_X_FORWARDED_FOR为47.23.56.89,127.168.1.1,47.12.45.78。

通过上面可以看出来,有多层代理时 X_REAL_IP是不对的,HTTP_X_FORWARDED_FOR里用户真实IP的位置是从后往前数=代理层数,从这里看网上的那个方法取第一个明显是不对的。

总结

获取方法还是要结合我们的架构,对自己的架构和使用的方法非常熟悉,才能写出没有bug的代码。