【问题标题】:How to use HTTP_X_FORWARDED_FOR properly?如何正确使用 HTTP_X_FORWARDED_FOR?
【发布时间】:2012-07-12 06:26:32
【问题描述】:

好的,我有一个小的身份验证问题。我的 Web 服务允许使用用户名和密码通过 HTTP 连接到我的 API,但此连接也可以限制为特定的 IP 地址。

这意味着$_SERVER['REMOTE_ADDR'] 可能不正确。我已经知道任何 IP 信息都不能真正被依赖——我有这个限制只是为了增加另一层安全性。

如果这是对我的网络服务器的请求的总体概述:

clientSERVER => clientPROXY => myPROXY => mySERVER

那么这意味着 mySERVER 显示 myPROXY 的 REMOTE_ADDR 而不是客户端的,并将客户端的实际 IP 发送为 HTTP_X_FORWARDED_FOR。

为了克服这个问题,我的 Web 服务有一个“受信任的代理”IP 地址列表,如果 REMOTE_ADDR 来自这些受信任的 IP 地址之一,那么它会告诉我的 Web 服务实际 IP 地址是 @ 的值987654327@.

现在问题出在 clientPROXY 上。这意味着(经常)mySERVER 获得具有多个 IP 地址的 HTTP_X_FORWARDED_FOR 值。根据HTTP_X_FORWARDED_FOR 文档,该值是以逗号分隔的 IP 地址列表,其中第一个 IP 是实际真实客户端的 IP 地址,其他每个 IP 地址都是代理的 IP 地址。

那么,如果HTTP_X_FORWARDED_FOR 有多个值并且我的服务受 IP 限制,我是否必须对照我的允许 IP 列表检查 HTTP_X_FORWARDED_FOR 的“最后一个”值并忽略实际的客户端 IP?

我假设在一个系统中,我必须设置允许的 IP 地址列表,列入白名单的 IP 地址应该是代理的地址,而不是代理后面的 IP(因为它可能是一些本地主机 IP 和经常更换)。

那么HTTP_CLIENT_IP呢?

【问题讨论】:

标签: php proxy ip forwarding


【解决方案1】:

您可以使用此功能获取正确的客户端IP:

public function getClientIP(){       
     if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){
            return  $_SERVER["HTTP_X_FORWARDED_FOR"];  
     }else if (array_key_exists('REMOTE_ADDR', $_SERVER)) { 
            return $_SERVER["REMOTE_ADDR"]; 
     }else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
            return $_SERVER["HTTP_CLIENT_IP"]; 
     } 

     return '';
}

【讨论】:

  • 这个答案没有解决问题中描述的具体情况,其中并非所有请求都通过代理传递。因此,直接从客户端接收的请求可能在标头中包含不准确的 IP 地址。
  • 不错的答案,您可以从 Prestashop 的工具类中找到更准确的答案:)。查找 getRemoteAddr() 函数
  • 这是一个可能的安全漏洞。任何人都可以将 X-Forwarded-For 标头添加到他们的请求中。
  • @frodeborli:你是对的;这就是为什么应该在 haproxy 中使用 reqidel+reqadd 指令的原因。
【解决方案2】:

我喜欢 Hrishikesh 的回答,我只有这个要添加...因为我们看到在使用多个代理时遇到了一个逗号分隔的字符串,我们发现有必要添加一个爆炸并抓住最后值,像这样:

$IParray=array_values(array_filter(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR'])));
return reset($IParray);

array_filter 用于删除空条目。

【讨论】:

  • 请注意,使用列表中的最后一个值似乎仍可能使用代理的 IP。根据下面的链接,原始客户端是 FIRST IP。 en.wikipedia.org/wiki/X-Forwarded-For
  • 另请注意,同一消息来源说这很容易伪造,因此最后一个更可靠。所以每个用例可能会做出不同的选择。如果获取 IP 的用例是打击欺诈或垃圾邮件,那么第一个 IP 可能毫无意义,而最可靠的地址 - 最后一个 - 最有用。如果获取 IP 的用例是不那么邪恶的活动,那么第一个将是最有用的。
【解决方案3】:

鉴于最新的httpoxy漏洞,确实需要一个完整的例子,如何正确使用HTTP_X_FORWARDED_FOR。

所以这里有一个用 PHP 编写的示例,如果您知道客户端可能在代理后面并且您知道该代理是可以信任的,那么如何检测客户端 IP 地址。如果您不知道任何受信任的代理,请使用 REMOTE_ADDR

<?php

function get_client_ip ()
{
    // Nothing to do without any reliable information
    if (!isset ($_SERVER['REMOTE_ADDR'])) {
        return NULL;
    }

    // Header that is used by the trusted proxy to refer to
    // the original IP
    $proxy_header = "HTTP_X_FORWARDED_FOR";

    // List of all the proxies that are known to handle 'proxy_header'
    // in known, safe manner
    $trusted_proxies = array ("2001:db8::1", "192.168.50.1");

    if (in_array ($_SERVER['REMOTE_ADDR'], $trusted_proxies)) {

        // Get the IP address of the client behind trusted proxy
        if (array_key_exists ($proxy_header, $_SERVER)) {

            // Header can contain multiple IP-s of proxies that are passed through.
            // Only the IP added by the last proxy (last IP in the list) can be trusted.
            $proxy_list = explode (",", $_SERVER[$proxy_header]);
            $client_ip = trim (end ($proxy_list));

            // Validate just in case
            if (filter_var ($client_ip, FILTER_VALIDATE_IP)) {
                return $client_ip;
            } else {
                // Validation failed - beat the guy who configured the proxy or
                // the guy who created the trusted proxy list?
                // TODO: some error handling to notify about the need of punishment
            }
        }
    }

    // In all other cases, REMOTE_ADDR is the ONLY IP we can trust.
    return $_SERVER['REMOTE_ADDR'];
}

print get_client_ip ();

?>

【讨论】:

  • 这是一个很好的答案,但有一个小问题。如果启用了严格的错误报告,则在一行中尝试trim(end(explode())) 将返回“仅应通过引用传递变量”。要解决这个问题,请先将分解的代理标头设置为变量,然后再将 trim(end()) 设置为。
  • @BenDyer 谢谢,已修复。
  • 我认为这是不正确的。按照Mozilla的说法,原IP是第一个,不是最后一个,所以应该是trim(array_shift())developer.mozilla.org/en-US/docs/Web/HTTP/Headers/…
【解决方案4】:

您也可以通过 Apache 配置使用 mod_remoteip 解决此问题,方法是将以下内容添加到 conf.d 文件中:

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 172.16.0.0/12
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

【讨论】:

    【解决方案5】:

    如果你在数据库中使用它,这是一个好方法:

    将数据库中的ip字段设置为varchar(250),然后使用这个:

    $theip = $_SERVER["REMOTE_ADDR"];
    
    if (!empty($_SERVER["HTTP_X_FORWARDED_FOR"])) {
        $theip .= '('.$_SERVER["HTTP_X_FORWARDED_FOR"].')';
    }
    
    if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
        $theip .= '('.$_SERVER["HTTP_CLIENT_IP"].')';
    }
    
    $realip = substr($theip, 0, 250);
    

    然后您只需对照数据库 ip 字段检查 $realip

    【讨论】:

      【解决方案6】:

      请注意,“HTTP_X_FORWARDED_FOR”很容易被欺骗,例如使用浏览器插件更改条目...所以请注意!

      试试这个来自 Hrishikesh Mishra 的修改后的代码(未测试):

       function getClientIP(){  
      
       if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) && preg_match('/\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b/', $_SERVER["HTTP_X_FORWARDED_FOR"])){
              return  $_SERVER["HTTP_X_FORWARDED_FOR"];  
       }else if (array_key_exists('REMOTE_ADDR', $_SERVER)) { 
              return $_SERVER["REMOTE_ADDR"]; 
       }else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
              return $_SERVER["HTTP_CLIENT_IP"]; 
       } 
      
       return '';
      }
      

      【讨论】:

        【解决方案7】:

        HTTP_CLIENT_IP 是获取用户 IP 地址的最可靠方法。接下来是 HTTP_X_FORWARDED_FOR,然后是 REMOTE_ADDR。按顺序检查所有三个,假设设置的第一个(isset($_SERVER['HTTP_CLIENT_IP']) 如果设置了该变量,则返回 true)是正确的。您可以使用各种方法独立检查用户是否正在使用代理。签出this。

        【讨论】:

        • 这些标题中没有一个比其他标题“更可靠”。它们都可以由客户端伪造,您的应用程序需要知道什么是可信的,什么是不可信的:您通常知道您的基础设施以及 HTTP 服务器前面的任何代理/负载平衡器的 IP 地址。
        • 没错,但一般来说,如果要根据假设的可靠性建立优先顺序,就是这样。我意识到它们都可以被操纵,但是如果必须使用它们,这就是如何做到的。
        • 为什么有这么多关于这个话题的虚假信息? $_SERVER['REMOTE_ADDR'] 是唯一不受远程用户影响的可靠字段,所有其他字段均从标头解析。
        • @transistor09 愿意提供您自己的答案吗?
        • 在我的网站上,$_SERVER['HTTP_CLIENT_IP'] 未设置,$_SERVER['REMOTE_ADDR'] 在每次页面加载时都会更改,而$_SERVER['HTTP_X_FORWARDED_FOR'] 是常量并设置为预期值。
        猜你喜欢
        • 2010-10-19
        • 1970-01-01
        • 2020-12-04
        • 2013-01-21
        • 2021-10-24
        • 2011-12-22
        • 2020-10-14
        • 2015-12-10
        相关资源
        最近更新 更多