CloudFlare 和 PHP 中记录访问者 IP 地址的日志

113
我正在尝试使用PHP的$_SERVER['REMOTE_ADDR']跟踪和记录访问我的网站的用户/访客。这是在PHP中跟踪IP地址的典型方法。
然而,我正在使用CloudFlare进行缓存等操作,并收到他们的IP地址作为CloudFlare的地址:

108.162.212.* - 108.162.239.*

在仍然使用CloudFlare的情况下,检索实际用户/访客的IP地址的正确方法是什么?
13个回答

315

Cloudflare可以使用的额外服务器变量包括:

$_SERVER["HTTP_CF_CONNECTING_IP"] 真实访客IP地址,这是您想要的

$_SERVER["HTTP_CF_IPCOUNTRY"] 访客所在国家

$_SERVER["HTTP_CF_RAY"]

$_SERVER["HTTP_CF_VISITOR"] 这可以帮助您知道是否是HTTP或HTTPS请求

您可以像这样使用它:

if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
  $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}
如果您这样做,访问IP地址的有效性很重要,您可能需要验证 $_SERVER["REMOTE_ADDR"] 是否包含实际有效的Cloudflare IP地址,因为任何人都可以伪造标头,如果他能够直接连接到服务器IP。

22
+1 替换 REMOTE_ADDR 为真实 IP,这样代码中就不需要进行其他修改。 - aalaap
12
如果安全很重要,就应该检查请求是否来自Cloudflare IP范围(https://www.cloudflare.com/ips)。 例如,在这个Joomla!插件中:https://github.com/piccaso/joomla-cf/blob/master/cf/cf.php - Florian Fida
1
Cloudflare也在他们的网站上列出了这段代码。https://support.cloudflare.com/hc/en-us/articles/200170876-I-can-t-install-mod-cloudflare-and-there-s-no-plugin-to-restore-original-visitor-IP-What-should-I-do- - C0nw0nk
2
最佳答案,同时也要赞扬包括HTTP_CF_IPCOUNTRY,我以前没有遇到过这个。 - David Bell
1
@michael 这是因为它返回的是你的IPv6地址。 - Lewis Browne
显示剩余4条评论

19

更新:CloudFlare已经为Apache发布了一个模块mod_cloudflare,该模块将记录并显示实际访问者的IP地址,而不是由Cloudflare访问的地址!https://www.cloudflare.com/resources-downloads#mod_cloudflare(回答者:olimortimer

如果您无法访问Apache运行时,则可以使用以下脚本,这将允许您检查连接是否通过Cloudflare并获取用户的IP地址。

我正在重写我为另一个问题所使用的答案“CloudFlare DNS/direct IP identifier

Cloudflare的IP地址是公开存储的,所以您可以在这里查看它们,然后检查IP地址是否来自Cloudflare(这将允许我们从HTTP头中获取真实的IP地址HTTP_CF_CONNECTING_IP)。

如果您要使用它来禁用所有非CF连接或反之,则建议您有一个单独的PHP脚本文件,在每个其他脚本之前调用该文件,例如common.php或pagestart.php等。

function ip_in_range($ip, $range) {
    if (strpos($range, '/') == false)
        $range .= '/32';

    // $range is in IP/CIDR format eg 127.0.0.1/24
    list($range, $netmask) = explode('/', $range, 2);
    $range_decimal = ip2long($range);
    $ip_decimal = ip2long($ip);
    $wildcard_decimal = pow(2, (32 - $netmask)) - 1;
    $netmask_decimal = ~ $wildcard_decimal;
    return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal));
}

function _cloudflare_CheckIP($ip) {
    $cf_ips = array(
        '199.27.128.0/21',
        '173.245.48.0/20',
        '103.21.244.0/22',
        '103.22.200.0/22',
        '103.31.4.0/22',
        '141.101.64.0/18',
        '108.162.192.0/18',
        '190.93.240.0/20',
        '188.114.96.0/20',
        '197.234.240.0/22',
        '198.41.128.0/17',
        '162.158.0.0/15',
        '104.16.0.0/12',
    );
    $is_cf_ip = false;
    foreach ($cf_ips as $cf_ip) {
        if (ip_in_range($ip, $cf_ip)) {
            $is_cf_ip = true;
            break;
        }
    } return $is_cf_ip;
}

function _cloudflare_Requests_Check() {
    $flag = true;

    if(!isset($_SERVER['HTTP_CF_CONNECTING_IP']))   $flag = false;
    if(!isset($_SERVER['HTTP_CF_IPCOUNTRY']))       $flag = false;
    if(!isset($_SERVER['HTTP_CF_RAY']))             $flag = false;
    if(!isset($_SERVER['HTTP_CF_VISITOR']))         $flag = false;
    return $flag;
}

function isCloudflare() {
    $ipCheck        = _cloudflare_CheckIP($_SERVER['REMOTE_ADDR']);
    $requestCheck   = _cloudflare_Requests_Check();
    return ($ipCheck && $requestCheck);
}

// Use when handling ip's
function getRequestIP() {
    $check = isCloudflare();

    if($check) {
        return $_SERVER['HTTP_CF_CONNECTING_IP'];
    } else {
        return $_SERVER['REMOTE_ADDR'];
    }
}

使用这个脚本非常简单:

$ip = getRequestIP();
$cf = isCloudflare();

if($cf) echo "Connection is through cloudflare: <br>";
else    echo "Connection is not through cloudflare: <br>";

echo "Your actual ip address is: ". $ip;
echo "The server remote address header is: ". $_SERVER['REMOTE_ADDR'];

此脚本将显示您的真实IP地址以及请求是否为CF!


1
这是正确的答案,但请记住您需要定期更新IP地址。上面代码中的当前IP已经失效,因此代码将失败。您可以在此处获取最新的IP地址:https://www.cloudflare.com/ips/理想情况下,您应该将它们存储在文件中,以便只需更新外部文件或者更好的方法是定期从此处获取:https://www.cloudflare.com/ips-v4 - rafark

15

Cloudflare会向您的服务器发送一些额外的请求头,包括CF-Connecting-IP,我们可以使用这个简单的单行命令将其存储到$user_ip中(如果已定义):

$user_ip = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR'];

1
问题在于这只是一个简单的标头,所以任何人都可以将其设置为他们想要的任何IP地址,这是一个巨大的安全漏洞。 - rafark
1
@rafark 如果你的服务器配置正确并且在Cloudflare后面,那就不会有问题。 - Sainan
当然可以。这本身是一个不错的解决方案,但在使用HTTP_CF_CONNECTING_IP之前仍应进行一些检查,就像Callum's answer中所述。 - rafark
@rafark 我也同意,但这是大量的 PHP 代码,而且在你的 Apache 配置中已经以另一种形式存在(我希望如此),这段代码更多是为了方便调试,如果攻击者可以访问你的调试环境,那么你有更重要的问题需要处理。 - Sainan

12

2
2019年 - 他们不再支持mod_cloudflare,但它仍然可以使用。现在有一个新的东西叫做mod_remoteip。参考:https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs-Logging-visitor-IP-addresses-with-mod-cloudflare- - sinaza
@sinaza 最好创建一个新答案,因为我对于 mod_cloudflare 的回答是4年前的。 - olimortimer
@sjagr关于您的编辑,它们是不正确的 - CloudFlare实际上是Cloudflare,就像我最初发布的那样。 - olimortimer

3

将HTTP_CF_CONNECTING_IP转换为REMOTE_ADDR可能很困难。因此,您可以使用apache(.htaccess)的自动前置来完成此操作。这样,您就不需要在所有PHP脚本中考虑$_SERVER ['REMOTE_ADDR']是否具有正确的值。

.htaccess代码

php_value auto_prepend_file "/path/to/file.php"

PHP代码(file.php)

<?php

define('CLIENT_IP', isset($_SERVER['HTTP_CF_CONNECTING_IP']) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : $_SERVER['REMOTE_ADDR']);

了解更多,请点击此处


2

最好在线检查Cloudflare IP范围(因为它可能随时更改),然后再检查是否来自Cloudflare。

Cloudflare IP txt

如果来源是Cloudflare,您可以使用$_SERVER['HTTP_CF_CONNECTING_IP']获取客户端请求的IP地址,但不建议在所有请求中都使用它,因为它可以由任何用户在请求头中发送以欺骗您。

您可以使用以下代码获取客户端请求的真实IP地址:

function _getUserRealIP() {
    $ipaddress = '';
    if(isset($_SERVER['REMOTE_ADDR']))
        $ipaddress = $_SERVER['REMOTE_ADDR'];
    else 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
        $ipaddress = 'UNKNOWN';
    return $ipaddress;
}

function _readCloudflareIps()
{
    $file = file("https://www.cloudflare.com/ips-v4",FILE_IGNORE_NEW_LINES);
    return $file;
}

function _checkIpInRange($ip, $range) {
    if (strpos($range, '/') == false)
        $range .= '/32';

    // $range is in IP/CIDR format eg 127.0.0.1/24
    list($range, $netmask) = explode('/', $range, 2);
    $range_decimal = ip2long($range);
    $ip_decimal = ip2long($ip);
    $wildcard_decimal = pow(2, (32 - $netmask)) - 1;
    $netmask_decimal = ~ $wildcard_decimal;
    return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal));
}

function _checkIsCloudflare($ip) {
    $cf_ips = _readCloudflareIps();
    $is_cf_ip = false;
    foreach ($cf_ips as $cf_ip) {
        if (_checkIpInRange($ip, $cf_ip)) {
            $is_cf_ip = true;
            break;
        }
    }
    return $is_cf_ip;
}

function getRealIp()
{
    $httpIp = _getUserRealIP();
    $check = _checkIsCloudflare($httpIp);
    if ($check) {
        return $_SERVER['HTTP_CF_CONNECTING_IP'];
    }else{
        return $httpIp;
    }
}

在将这些函数导入到您的代码后,您只需要调用getRealIp()函数,如下所示:
$userIp = getRealIp();
echo $userIp();

  1. REMOTE_ADDR 始终包含有效数据,因此其他条件永远不会执行。
  2. 每次请求都下载文件并不是一个好主意,最好将其缓存6小时。
- a55
1
@a55 这是真的,最好将从Cloudflare下载的IP列表缓存起来。关于 $_SERVER['REMOTE_ADDR'],这是检查用户真实IP并防止IP欺诈的最安全方式,但也有其他方法可以使用,我试图展示给你们看。 - Majid Jalilian

2
Cloudflare在网络管理页面上有一个“伪IPv4”地址选项,您可以选择添加或覆盖发送到您服务器的标头。

Cloudflare Dashboard | Network

从文档中可以看到:

什么是伪IPv4?

为了加速IPv6的采用,Cloudflare提供了Pseudo IPv4,它支持在期望IPv4地址的旧应用程序中使用IPv6地址。目标是为每个IPv6地址提供一个几乎唯一的IPv4地址,使用类E IPv4地址空间,该地址空间被指定为实验性的,通常不会看到流量。要了解更多信息,请参见这里

选项

  • 添加标头:仅添加附加的Cf-Pseudo-IPv4标头
  • 覆盖标头:使用伪IPv4地址覆盖现有的Cf-Connecting-IPX-Forwarded-For标头。

注意:我们建议将其设置为“关闭”,除非您有特定需求。

您可以从Cloudflare的这篇文章中了解更多关于此功能的信息。该文章详细介绍了他们如何在IPv4的32位空间中尝试适应IPv6的128位地址空间。
如果您想要了解伪IPv4地址和IPv6地址,当启用伪IPv4时,Cloudflare会添加一个头。这可用于衡量有多少流量来自于IPv6网络上的设备,如果您需要一个实际数字向管理层展示,在更新仍受限于IPv4限制的系统之前进行投资将非常有用。

感谢您的解释。我尝试了伪IPv4,但它似乎不是实际的IPv4地址。我可以得到246.101.74.149,但我的IP并不是这个。我查找了IP并得到了“bogon:true”和“Reserved RFC3330”。 - CHOO YJ
仅支持IPv6的网络连接,除非中间设备分配虚拟IP地址,否则不会分配IPv4地址。您是如何确定您的IPv4地址的? - matigo
我使用了一个在线工具来检查这台电脑的IPv4地址:https://whatismyipaddress.com/,这个地址与我的路由器网站上显示的IP地址相同。 - CHOO YJ
我尝试了一些设备,这是我的结果:我的电脑使用WiFi连接:虚拟IP,另一台使用相同WiFi的电脑:虚拟IP,我的手机使用相同WiFi连接:正确的IP,我的手机连接到移动数据:虚拟IP。 - CHOO YJ
两天前我没有收到你的任何回复。我将授予最佳现有答案全部赏金金额。唯一的结论是,没有什么可以获得真正的IPv4,因为我的计算机就是一个例子,伪IPv4可以给我虚假的IPv4。 - CHOO YJ

2
当您使用CloudFlare时,您与用户之间的所有请求都通过CloudFlare服务器路由。
在这种情况下,有两种方法可以获得用户的真实IP地址:
1. 通过CloudFlare服务器添加的额外服务器头 2. 在您的服务器上添加CloudFlare Apache/NGINX模块。
方法一:通过额外的服务器头获取IP
您可以使用以下代码来获取用户的IP地址: $user_ip = (isset($_SERVER["HTTP_CF_CONNECTING_IP"]) $_SERVER["HTTP_CF_CONNECTING_IP"]:$_SERVER['REMOTE_ADDR']);
它是如何工作的?
CloudFlare在请求中添加了一些额外的服务器变量,如下所示: $_SERVER["HTTP_CF_CONNECTING_IP"] - 用户的真实IP地址 $_SERVER["HTTP_CF_IPCOUNTRY"] - 用户的ISO2国家 $_SERVER["HTTP_CF_RAY"] - 用于记录目的的特殊字符串
在上面的代码中,我们正在检查是否设置了$_SERVER["HTTP_CF_CONNECTING_IP"]。如果存在,我们将把它视为用户的IP地址,否则我们将使用默认代码$_SERVER['REMOTE_ADDR']
方法2:在您的服务器上安装Cloudflare模块

2
在Laravel中,将以下行添加到AppServiceProvider中:
...
use Symfony\Component\HttpFoundation\Request;


...
public function boot()
{
    Request::setTrustedProxies(['REMOTE_ADDR'], Request::HEADER_X_FORWARDED_FOR);
}

现在你可以使用request()->ip()来获取真实的客户端IP。

更多信息请查看这里


这个处理 CF 服务器变量吗?还是我们还需要调用 Request::setTrustedProxies(['REMOTE_ADDR'], 'HTTP_CF_CONNECTING_IP'); - Sᴀᴍ Onᴇᴌᴀ

2

只有在使用 Cloudflare 时,HTTP_CF_CONNECTING_IP 才有效。如果你转移了你的网站或删除了 Cloudflare,你将会忘记这个值,所以请使用以下代码。

$ip=$_SERVER["HTTP_CF_CONNECTING_IP"];
if (!isset($ip)) {
  $ip = $_SERVER['REMOTE_ADDR'];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接