题 如何在PHP中获取客户端IP地址?


如何使用PHP获取客户端IP地址?

我想记录通过他/她的IP地址登录我网站的用户。


982
2018-06-09 04:50


起源


有关要记录的内容的建议,请参阅RFC6302,特别是现在请记住记录端口,而不仅仅是地址。 - Patrick Mevzek
对于跟踪用户的一个警告,在全球多个地区,ISPS正在使用CGNAT,这使得信任纯粹的IP地址变得更加复杂 - Jonathan


答案:


无论您做什么,请确保不要信任从客户端发送的数据。 $_SERVER['REMOTE_ADDR'] 包含连接方的真实IP地址。这是您可以找到的最可靠的价值。

但是,它们可以在代理服务器后面,在这种情况下代理可能已经设置了 $_SERVER['HTTP_X_FORWARDED_FOR'],但这个值很容易被欺骗。例如,它可以由没有代理的人设置,或者IP可以是来自代理后面的LAN的内部IP。

这意味着,如果你要保存 $_SERVER['HTTP_X_FORWARDED_FOR'], 确保你  保存 $_SERVER['REMOTE_ADDR'] 值。例如。将两个值保存在数据库的不同字段中。

如果要将IP作为字符串保存到数据库,请确保至少有空间 45个字符IPv6的 就在这里,这些地址大于旧的IPv4地址。

(请注意,IPv6通常最多使用39个字符,但也有一个特殊字符 IPv4地址的IPv6表示法 完整形式,最多45个字符。因此,如果您知道自己在做什么,可以使用39个字符,但如果您只想设置并忘记它,请使用45)。


1166
2018-06-09 05:15



很好的答案!我已经在我的服务器上使用$ _SERVER ['REMOTE_ADDR'],我喜欢你包含另一种方式,以及优点和缺点。 - Blue
注意:  REMOTE_ADDR 威力 不 包含TCP连接的真实IP。这完全取决于您的SAPI。确保正确配置SAPI $_SERVER['REMOTE_ADDR'] 实际上返回TCP连接的IP。如果失败可能会导致一些严重的漏洞,例如,StackExchange曾经授予过 管理员访问 通过检查 REMOTE_ADDR 看看它是否匹配“localhost”,不幸的是SAPI的配置..................................... ...................................... - Pacerier
.................................................. ........................有一个漏洞(它需要 HTTP_X_FORWARDED_FOR作为输入)允许非管理员通过改变来获得管理员访问权限 HTTP_X_FORWARDED_FOR 头。另见 blog.ircmaxell.com/2012/11/anatomy-of-attack-how-i-hacked.html - Pacerier
什么是 $_SERVER['HTTP_CLIENT_IP']) ? - stack
@chiragpatel是的,确实如此。否则,您的服务器配置非常糟糕。 - Emil Vikström


$_SERVER['REMOTE_ADDR'] 实际上可能不包含真实的客户端IP地址,因为它将为您提供通过代理连接的客户端的代理地址。那可能 不过,这取决于你对IP的做法。如果您说,尝试查看您的流量源自何处,或者记住用户上次连接的IP,代理或NAT网关的公共IP可能更多,那么某人的私有RFC1918地址可能对您没有任何帮助。适合存放。

有几个HTTP标头,如 X-Forwarded-For 可以由各种代理设置也可以不设置。问题是那些只是HTTP标头,任何人都可以设置。他们的内容无法保证。 $_SERVER['REMOTE_ADDR'] 是Web服务器从其接收连接并将响应发送到的实际物理IP地址。其他任何事情都只是随意和自愿的信息。只有一种情况可以信任此信息:您正在控制设置此标头的代理。只有当你知道100%在哪里以及如何设置标题时才意味着你应该注意它的重要性。

话虽如此,这里有一些示例代码:

if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $ip = $_SERVER['REMOTE_ADDR'];
}

编者注: 使用上面的代码了 安全隐患。客户端可以设置所有HTTP头信息(即。 $_SERVER['HTTP_...)它想要的任意值。因此,它使用起来更可靠 $_SERVER['REMOTE_ADDR'],因为这不能由用户设置。

从: http://roshanbh.com.np/2007/12/getting-real-ip-address-in-php.html


377
2017-09-11 04:01



除非你完全知道它的作用,否则不要使用上面的代码! 由于这个原因,我已经看到了MASSIVE安全漏洞。客户端可以设置 X-Forwarded-For 或者 Client-IP 标题到它想要的任意值。除非您有可信的反向代理,否则不应使用任何这些值。 - Janos Pasztor
关于Janoszen的评论,一个选项是PHP的filter_var($ _ SERVER ['REMOTE_ADDR'],FILTER_VALIDATE_IP)。 - lostphilosopher
X-Forwarded-For 可能包含多个IP地址,以逗号分隔;应该真正“解析”而不是采用面值(AFAIK,它几乎 决不 包含单个IP)。 - Martin Tournoij
@lostphilosopher这是一个合理的事情,它会使它更可靠,但不幸的是它仍然允许欺骗。 - Tim Seguine
对于缩放大小的站点,Web应用程序服务器前面将有负载平衡器和/或反向代理。您必须配置这些负载平衡器或代理以删除任何外部X-Forwarded-For标头,而是插入他们自己的连接客户端所看到的IP地址。 - Jon Watte


echo $_SERVER['REMOTE_ADDR'];

http://php.net/manual/en/reserved.variables.server.php


185
2018-06-09 04:51



@Anup Prakash这就是它 - 因此是“REMOTE”(从剧本的角度来看)。 - Artefacto
我在使用你的代码时得到这个: ::1 - Si8
因为你在localhost;) - Ussama Dahnin
@ SiKni8 ::1 是IPv6的等价物 127.0.0.1 - Camilo Martin
@CamiloMartin你刚刚教我一些东西。凉! - Kristian


这是获取用户ip的好方法的清洁代码示例。

$ip = $_SERVER['HTTP_CLIENT_IP']?$_SERVER['HTTP_CLIENT_IP']:($_SERVER['HTTP_X_FORWARDE‌​D_FOR']?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR']);

这是一个使用elvis运算符的较短版本

$_SERVER['HTTP_CLIENT_IP']?:($_SERVER['HTTP_X_FORWARDE‌​D_FOR']?:$_SERVER['REMOTE_ADDR']);

这是一个使用isset删除通知的版本(谢谢,@ shasi kanth)

$ip = isset($_SERVER['HTTP_CLIENT_IP'])?$_SERVER['HTTP_CLIENT_IP']:isset($_SERVER['HTTP_X_FORWARDED_FOR'])?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR'];

80
2017-10-08 16:20



始终记住清理可能已被用户修改的任何输入。这是其中一次。 - josh123a123
我相信代码缺少一些表达式并且优先级的顺序是反转的,所以它应该是这样的: $ip = $_SERVER['HTTP_CLIENT_IP']?$_SERVER['HTTP_CLIENT_IP']:($_SERVER['HTTP_X_FORWARDED_FOR']?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR']); 不过,非常好。 - DavidTaubmann
接得好。我调整了帖子。谢谢! - josh123a123
作为一个功能,过滤: function getUserIP() { $client = @$_SERVER['HTTP_CLIENT_IP']; $forward = @$_SERVER['HTTP_X_FORWARDED_FOR']; return filter_var($client, FILTER_VALIDATE_IP) ? $client : filter_var($forward, FILTER_VALIDATE_IP) ? $forward : $_SERVER['REMOTE_ADDR']; } - SpYk3HH
刚添加了isset()来删除通知: $ ip = isset($ _ SERVER ['HTTP_CLIENT_IP'])?$ _ SERVER ['HTTP_CLIENT_IP']:isset($ _ SERVER ['HTTP_X_FORWARDE D_FOR'])?$ _ SERVER ['HTTP_X_FORWARDED_FOR']:$ _ SERVER ['REMOTE_ADDR'] ; - shasi kanth


它应该包含在 $_SERVER['REMOTE_ADDR'] 变量。


79
2017-09-11 03:38





我最喜欢的解决方案是Zend Framework 2使用的方式。它也考虑了 $_SERVER 性能 HTTP_X_FORWARDED_FORHTTP_CLIENT_IPREMOTE_ADDR 但它声明了一个类来设置一些受信任的代理,它返回一个IP地址而不是一个数组。我认为这是最接近它的解决方案:

class RemoteAddress
{
    /**
     * Whether to use proxy addresses or not.
     *
     * As default this setting is disabled - IP address is mostly needed to increase
     * security. HTTP_* are not reliable since can easily be spoofed. It can be enabled
     * just for more flexibility, but if user uses proxy to connect to trusted services
     * it's his/her own risk, only reliable field for IP address is $_SERVER['REMOTE_ADDR'].
     *
     * @var bool
     */
    protected $useProxy = false;

    /**
     * List of trusted proxy IP addresses
     *
     * @var array
     */
    protected $trustedProxies = array();

    /**
     * HTTP header to introspect for proxies
     *
     * @var string
     */
    protected $proxyHeader = 'HTTP_X_FORWARDED_FOR';

    // [...]

    /**
     * Returns client IP address.
     *
     * @return string IP address.
     */
    public function getIpAddress()
    {
        $ip = $this->getIpAddressFromProxy();
        if ($ip) {
            return $ip;
        }

        // direct IP address
        if (isset($_SERVER['REMOTE_ADDR'])) {
            return $_SERVER['REMOTE_ADDR'];
        }

        return '';
    }

    /**
     * Attempt to get the IP address for a proxied client
     *
     * @see http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.2
     * @return false|string
     */
    protected function getIpAddressFromProxy()
    {
        if (!$this->useProxy
            || (isset($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], $this->trustedProxies))
        ) {
            return false;
        }

        $header = $this->proxyHeader;
        if (!isset($_SERVER[$header]) || empty($_SERVER[$header])) {
            return false;
        }

        // Extract IPs
        $ips = explode(',', $_SERVER[$header]);
        // trim, so we can compare against trusted proxies properly
        $ips = array_map('trim', $ips);
        // remove trusted proxy IPs
        $ips = array_diff($ips, $this->trustedProxies);

        // Any left?
        if (empty($ips)) {
            return false;
        }

        // Since we've removed any known, trusted proxy servers, the right-most
        // address represents the first IP we do not know about -- i.e., we do
        // not know if it is a proxy server, or a client. As such, we treat it
        // as the originating IP.
        // @see http://en.wikipedia.org/wiki/X-Forwarded-For
        $ip = array_pop($ips);
        return $ip;
    }

    // [...]
}

请在此处查看完整代码: https://raw.githubusercontent.com/zendframework/zend-http/master/src/PhpEnvironment/RemoteAddress.php


47
2017-07-07 09:02



很棒的答案!使用经过生产测试的代码,在如此大的框架中开发和使用是您可以做的最好的事情之一:) - jnhghy - Alexandru Jantea
所以等待zend没有过滤任何东西?我应该看到类似:filter_var($ _SERVER ['REMOTE_ADDR'],FILTER_VALIDATE_IP,FILTER_FLAG_IPV4); - M H
@Hanoncs你为什么要这样做?欺骗远程地址非常困难 - Marius.C
@Hanoncs我认为你必须在获得这个课程后检查它。它不是它逻辑的一部分。它只是从中得到了价值 $_SERVER 变量原样并跳过一些已定义和众所周知的代理服务器。就这样。如果您认为返回值不安全,请检查它或向PHP开发人员报告错误。 - algorhythm
如果您的业务网络解决方案适用于您已知的代理服务器,则这是有意义的。从这里的某个地方定义一些代理服务器是没有意义的。 - algorhythm


互联网背后有不同类型的用户,因此我们希望从不同的魔药中捕获IP地址。那是,

1。 $_SERVER['REMOTE_ADDR']  - 它包含客户端的真实IP地址。这是您可以从用户那里找到的最可靠的值。

2。 $_SERVER['REMOTE_HOST']  - 这将获取用户正在查看当前页面的主机名。 但是要使此脚本起作用,必须配置httpd.conf中的Hostname Lookups On。

3。 $_SERVER['HTTP_CLIENT_IP']  - 当用户来自共享Internet服务时,这将获取IP地址。

4。 $_SERVER['HTTP_X_FORWARDED_FOR']  - 当他在代理后面时,这将从用户获取IP地址

因此,我们可以使用以下组合功能来获取在不同位置查看的用户的真实IP地址,

// Function to get the user IP address
function getUserIP() {
    $ipaddress = '';
    if (isset($_SERVER['HTTP_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_X_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
    else if(isset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_FORWARDED'];
    else if(isset($_SERVER['REMOTE_ADDR']))
        $ipaddress = $_SERVER['REMOTE_ADDR'];
    else
        $ipaddress = 'UNKNOWN';
    return $ipaddress;
}

35
2017-12-29 15:18



真的很容易假。我只是通过将Client-ip标头设置为“111.111.111.111”在我自己的网站上尝试使用Requestly Chrome扩展程序。 - Soaku


这是我发现的最先进的方法,过去已经尝试了其他方法。有效以确保获取访问者的IP地址(但请注意,任何黑客都可以轻松伪造IP地址)。

function get_ip_address() {
    // check for shared internet/ISP IP
    if (!empty($_SERVER['HTTP_CLIENT_IP']) && validate_ip($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    }

    // check for IPs passing through proxies
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        // check if multiple ips exist in var
        if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false) {
            $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            foreach ($iplist as $ip) {
                if (validate_ip($ip))
                    return $ip;
            }
        } else {
            if (validate_ip($_SERVER['HTTP_X_FORWARDED_FOR']))
                return $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED']) && validate_ip($_SERVER['HTTP_X_FORWARDED']))
        return $_SERVER['HTTP_X_FORWARDED'];
    if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
        return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
    if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
        return $_SERVER['HTTP_FORWARDED_FOR'];
    if (!empty($_SERVER['HTTP_FORWARDED']) && validate_ip($_SERVER['HTTP_FORWARDED']))
        return $_SERVER['HTTP_FORWARDED'];

    // return unreliable ip since all else failed
    return $_SERVER['REMOTE_ADDR'];
}

/**
 * Ensures an ip address is both a valid IP and does not fall within
 * a private network range.
 */
function validate_ip($ip) {
    if (strtolower($ip) === 'unknown')
        return false;

    // generate ipv4 network address
    $ip = ip2long($ip);

    // if the ip is set and not equivalent to 255.255.255.255
    if ($ip !== false && $ip !== -1) {
        // make sure to get unsigned long representation of ip
        // due to discrepancies between 32 and 64 bit OSes and
        // signed numbers (ints default to signed in PHP)
        $ip = sprintf('%u', $ip);
        // do private network range checking
        if ($ip >= 0 && $ip <= 50331647) return false;
        if ($ip >= 167772160 && $ip <= 184549375) return false;
        if ($ip >= 2130706432 && $ip <= 2147483647) return false;
        if ($ip >= 2851995648 && $ip <= 2852061183) return false;
        if ($ip >= 2886729728 && $ip <= 2887778303) return false;
        if ($ip >= 3221225984 && $ip <= 3221226239) return false;
        if ($ip >= 3232235520 && $ip <= 3232301055) return false;
        if ($ip >= 4294967040) return false;
    }
    return true;
}

资源: http://blackbe.lt/advanced-method-to-obtain-the-client-ip-in-php/


29
2018-01-29 14:37



这是错的。 HTTP_CLIENT_IP比REMOTE_ADDR更不可靠,并且ip验证功能是无意义的。 - tobltobs
@tobltobs。有趣的是你说这个,但这是唯一一个在重定向页面加载时在一个清漆盒后面实际工作的功能集。我赞不绝口。 - Mike Q


答案是使用 $_SERVER 变量。例如, $_SERVER["REMOTE_ADDR"] 将返回客户端的IP地址。


26
2018-06-09 04:56