【问题标题】:What is the most accurate way to retrieve a user's correct IP address in PHP?在 PHP 中检索用户正确 IP 地址的最准确方法是什么?
【发布时间】:2010-12-10 17:23:16
【问题描述】:

我知道有大量的 $_SERVER 变量标头可用于 IP 地址检索。我想知道对于如何使用所述变量最准确地检索用户的真实 IP 地址(众所周知,没有一种方法是完美的),是否存在普遍共识?

我花了一些时间试图找到一个深入的解决方案,并根据许多来源提出了以下代码。如果有人可以在答案中戳洞或阐明一些可能更准确的东西,我会很高兴。

编辑包括来自@Alix 的优化

 /**
  * Retrieves the best guess of the client's actual IP address.
  * Takes into account numerous HTTP proxy headers due to variations
  * in how different ISPs handle IP addresses in headers between hops.
  */
 public function get_ip_address() {
  // Check for shared internet/ISP IP
  if (!empty($_SERVER['HTTP_CLIENT_IP']) && $this->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 IP addresses exist in var
    $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
    foreach ($iplist as $ip) {
     if ($this->validate_ip($ip))
      return $ip;
    }
   }
  }
  if (!empty($_SERVER['HTTP_X_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_X_FORWARDED']))
   return $_SERVER['HTTP_X_FORWARDED'];
  if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && $this->validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
   return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
  if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && $this->validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
   return $_SERVER['HTTP_FORWARDED_FOR'];
  if (!empty($_SERVER['HTTP_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_FORWARDED']))
   return $_SERVER['HTTP_FORWARDED'];

  // Return unreliable IP address since all else failed
  return $_SERVER['REMOTE_ADDR'];
 }

 /**
  * Ensures an IP address is both a valid IP address and does not fall within
  * a private network range.
  *
  * @access public
  * @param string $ip
  */
 public function validate_ip($ip) {
     if (filter_var($ip, FILTER_VALIDATE_IP, 
                         FILTER_FLAG_IPV4 | 
                         FILTER_FLAG_IPV6 |
                         FILTER_FLAG_NO_PRIV_RANGE | 
                         FILTER_FLAG_NO_RES_RANGE) === false)
         return false;
     self::$ip = $ip;
     return true;
 }

警告词(更新)

REMOTE_ADDR 仍然代表最可靠的 IP 地址来源。这里提到的其他$_SERVER 变量很容易被远程客户端欺骗。此解决方案的目的是尝试确定位于代理后面的客户端的 IP 地址。出于一般目的,您可以考虑将其与直接从$_SERVER['REMOTE_ADDR'] 返回的 IP 地址结合使用并存储两者。

对于 99.9% 的用户而言,此解决方案将完全满足您的需求。它无法保护您免受 0.1% 的恶意用户的攻击,他们希望通过注入自己的请求标头来滥用您的系统。如果依赖 IP 地址完成关键任务,请使用 REMOTE_ADDR,不要费心迎合代理背后的人。

【问题讨论】:

  • 对于 whatismyip.com 的问题,我认为他们会执行类似此脚本的操作,您是否在本地运行它?如果这就是你拥有内部 IP 的原因,那么在这种情况下不会通过公共接口发送任何内容,因此 php 无法获取任何信息
  • 确保在实施时牢记这一点:stackoverflow.com/questions/1672827/…
  • 请记住,所有这些 HTTP 标头都非常容易修改:使用您的解决方案,我只需将浏览器配置为发送带有随机 IP 的 X-Forwarded-For 标头,您的脚本就会愉快地返回一个假地址。因此,根据您要执行的操作,此解决方案可能不如简单地使用 REMOTE_ADDR 可靠。
  • OMFG,“不可靠的 ip”!我第一次在这里看到这样的废话。唯一可靠的 IP 地址是 REMOTE_ADDR
  • -1 这很容易受到欺骗。您所做的只是询问用户他的 IP 地址应该是什么。

标签: php ip-address


【解决方案1】:

您几乎回答了自己的问题! :)

function getRealIpAddr() {
    if(!empty($_SERVER['HTTP_CLIENT_IP']))   //Check IP address from shared Internet
    {
        $IPaddress = $_SERVER['HTTP_CLIENT_IP'];
    }
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))   //To check IP address is passed from the proxy
    {
        $IPaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
    else
    {
        $IPaddress = $_SERVER['REMOTE_ADDR'];
    }
    return $IPaddress;
}

Source

【讨论】:

    【解决方案2】:

    即便如此,获取用户的真实 IP 地址仍将是不可靠的。他们需要做的就是使用匿名代理服务器(不支持http_x_forwarded_forhttp_forwarded 等的标头),而您得到的只是他们代理服务器的 IP 地址。

    然后您可以查看是否有匿名代理服务器 IP 地址列表,但无法确保它也是 100% 准确的,而且它最多只能让您知道它是代理服务器。如果有人很聪明,他们可以欺骗 HTTP 转发的标头。

    假设我不喜欢当地的大学。我弄清楚他们注册了哪些 IP 地址,并通过做坏事让他们的 IP 地址在您的网站上被禁止,因为我发现您尊重 HTTP 转发。这个列表是无穷无尽的。

    然后,正如您所猜想的那样,还有内部 IP 地址,例如我之前提到的大学网络。很多使用 10.x.x.x 格式。所以你只知道它是为共享网络转发的。

    那么我不会过多介绍它,但动态 IP 地址不再是宽带方式。所以。即使您获得了用户 IP 地址,预计它也会在 2 到 3 个月内发生变化,最长。

    【讨论】:

    • 感谢您的意见。我目前正在利用用户的 IP 地址来帮助进行会话身份验证,方法是使用他们的 C 类 IP 作为限制会话劫持的限制因素,但允许在合理范围内使用动态 IP。欺骗性 IP 和匿名代理服务器只是我必须为选定的一组人处理的事情。
    • @cballou - 为此目的,REMOTE_ADDR 肯定是正确的使用方法。任何依赖 HTTP 标头的方法都容易受到标头欺骗。一个会话多长时间?动态 IP 不会快速变化。
    • 他们会这样做,特别是如果我希望他们这样做(更改许多驱动程序支持的 mac 地址)。仅 REMOTE_ADDR 本身就足以获取它与之交谈的最后一个服务器。因此,在代理情况下,您将获得代理 IP。
    【解决方案3】:

    我们使用:

    /**
     * Get the customer's IP address.
     *
     * @return string
     */
    public function getIpAddress() {
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            return $_SERVER['HTTP_CLIENT_IP'];
        } else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            return trim($ips[count($ips) - 1]);
        } else {
            return $_SERVER['REMOTE_ADDR'];
        }
    }
    

    HTTP_X_FORWARDED_FOR 的爆炸是因为我们在使用 Squid 时检测到 IP 地址的奇怪问题。

    【讨论】:

    • 哎呀,我刚刚意识到你在 exploding on 上做的基本相同,等等。加上一点额外的。所以我怀疑我的回答是否有很大帮助。 :)
    • 返回本地主机的地址
    【解决方案4】:
    /**
     * Sanitizes IPv4 address according to Ilia Alshanetsky's book
     * "php|architect?s Guide to PHP Security", chapter 2, page 67.
     *
     * @param string $ip An IPv4 address
     */
    public static function sanitizeIpAddress($ip = '')
    {
    if ($ip == '')
        {
        $rtnStr = '0.0.0.0';
        }
    else
        {
        $rtnStr = long2ip(ip2long($ip));
        }
    
    return $rtnStr;
    }
    
    //---------------------------------------------------
    
    /**
     * Returns the sanitized HTTP_X_FORWARDED_FOR server variable.
     *
     */
    public static function getXForwardedFor()
    {
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
        {
        $rtnStr = $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
    elseif (isset($HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR']))
        {
        $rtnStr = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'];
        }
    elseif (getenv('HTTP_X_FORWARDED_FOR'))
        {
        $rtnStr = getenv('HTTP_X_FORWARDED_FOR');
        }
    else
        {
        $rtnStr = '';
        }
    
    // Sanitize IPv4 address (Ilia Alshanetsky):
    if ($rtnStr != '')
        {
        $rtnStr = explode(', ', $rtnStr);
        $rtnStr = self::sanitizeIpAddress($rtnStr[0]);
        }
    
    return $rtnStr;
    }
    
    //---------------------------------------------------
    
    /**
     * Returns the sanitized REMOTE_ADDR server variable.
     *
     */
    public static function getRemoteAddr()
    {
    if (isset($_SERVER['REMOTE_ADDR']))
        {
        $rtnStr = $_SERVER['REMOTE_ADDR'];
        }
    elseif (isset($HTTP_SERVER_VARS['REMOTE_ADDR']))
        {
        $rtnStr = $HTTP_SERVER_VARS['REMOTE_ADDR'];
        }
    elseif (getenv('REMOTE_ADDR'))
        {
        $rtnStr = getenv('REMOTE_ADDR');
        }
    else
        {
        $rtnStr = '';
        }
    
    // Sanitize IPv4 address (Ilia Alshanetsky):
    if ($rtnStr != '')
        {
        $rtnStr = explode(', ', $rtnStr);
        $rtnStr = self::sanitizeIpAddress($rtnStr[0]);
        }
    
    return $rtnStr;
    }
    
    //---------------------------------------------------
    
    /**
     * Returns the sanitized remote user and proxy IP addresses.
     *
     */
    public static function getIpAndProxy()
    {
    $xForwarded = self::getXForwardedFor();
    $remoteAddr = self::getRemoteAddr();
    
    if ($xForwarded != '')
        {
        $ip    = $xForwarded;
        $proxy = $remoteAddr;
        }
    else
        {
        $ip    = $remoteAddr;
        $proxy = '';
        }
    
    return array($ip, $proxy);
    }
    

    【讨论】:

      【解决方案5】:

      这是获取 IP 地址的一种更简洁、更简洁的方法:

      function get_ip_address(){
          foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key){
              if (array_key_exists($key, $_SERVER) === true){
                  foreach (explode(',', $_SERVER[$key]) as $ip){
                      $ip = trim($ip); // just to be safe
      
                      if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
                          return $ip;
                      }
                  }
              }
          }
      }
      

      您的代码似乎已经很完整了,我看不到任何可能的错误(除了通常的 IP 警告),我会更改 validate_ip() 函数以依赖过滤器扩展:

      public function validate_ip($ip)
      {
          if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false)
          {
              return false;
          }
      
          self::$ip = sprintf('%u', ip2long($ip)); // you seem to want this
      
          return true;
      }
      

      您的HTTP_X_FORWARDED_FOR sn-p 也可以从此简化:

      // 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 ($this->validate_ip($ip))
                      return $ip;
              }
          }
          
          else
          {
              if ($this->validate_ip($_SERVER['HTTP_X_FORWARDED_FOR']))
                  return $_SERVER['HTTP_X_FORWARDED_FOR'];
          }
      }
      

      到这里:

      // check for IPs passing through proxies
      if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
      {
          $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
              
          foreach ($iplist as $ip)
          {
              if ($this->validate_ip($ip))
                  return $ip;
          }
      }
      

      您可能还想验证 IPv6 地址。

      【讨论】:

      • 我非常感谢filter_var 修复,因为它消除了对 IP 地址的一堆骇人听闻的 unsigned int 检查。我也喜欢它让我也可以选择验证 IPv6 地址。 HTTP_X_FORWARDED_FOR 优化也非常受欢迎。几分钟后我会更新代码。
      • -1 这很容易受到欺骗,您所做的只是询问用户他的 IP 地址应该是什么。
      • @Rook:是的,我知道。 OP 知道这一点,我在回答中也提到了这一点。但是感谢您的评论。
      • 仅供参考:我必须删除 FILTER_FLAG_IPV6 才能让 Alix Axel 的代码正常工作。
      • @rubenrp81 TCP 套接字处理程序是唯一的规范来源,其他一切都受攻击者控制。上面的代码是攻击者的梦想。
      【解决方案6】:

      最大的问题是什么目的?

      您的代码几乎尽可能全面 - 但我看到,如果您发现看起来像添加了代理的标头,则使用 CLIENT_IP 的 INSTEAD,但是如果您希望此信息用于审计目的,请注意 -它很容易伪造。

      当然,您绝不应该将 IP 地址用于任何类型的身份验证 - 即使这些都可能被欺骗。

      您可以通过推出通过非 http 端口连接回服务器的 flash 或 java 小程序来更好地测量客户端 IP 地址(这将显示透明代理或代理注入标头的情况) false - 但请记住,如果客户端只能通过 Web 代理连接或传出端口被阻止,则不会有来自小程序的连接。

      【讨论】:

      • 考虑到我正在寻找仅 PHP 的解决方案,您是否建议我添加 $_SERVER['CLIENT_IP'] 作为第二个 else if 语句?
      • 否 - 如果您想对返回的数据赋予任何意义,那么最好保留网络端点地址(客户端 IP)以及任何暗示不同值的内容在代理添加的标头中(例如,您可能会看到很多 192.168.1.x 地址,但来自不同的客户端 IP)C.
      【解决方案7】:

      我确实想知道您是否应该以相反的顺序迭代分解的 HTTP_X_FORWARDED_FOR,因为我的经验是用户的 IP 地址最终位于逗号分隔列表的末尾,因此从标题的开头开始,您更有可能获得返回的代理之一的 IP 地址,这可能仍然允许会话劫持,因为许多用户可能会通过该代理。

      【讨论】:

      • 阅读了关于 HTTP_X_FORWARDED_FOR 的维基百科页面:en.wikipedia.org/wiki/X-Forwarded-For ...我看到建议的顺序确实是从左到右,因为您的代码有它。但是,从我们的日志中,我可以看到在很多情况下,野外代理不尊重这一点,并且您要检查的 IP 地址可能位于列表的任一端。
      • 或者在中间,如果一些代理遵守从左到右的顺序而其他代理不遵守,就会发生这种情况。
      【解决方案8】:

      谢谢你,非常有用。

      如果代码在语法上正确,这将有所帮助。因为它在第 20 行附近有一个{太多。恐怕这意味着没有人真正尝试过。

      我可能疯了,但是在尝试了几个有效和无效地址之后,唯一有效的 validate_ip() 版本是:

          public function validate_ip($ip)
          {
              if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false)
                  return false;
              if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) === false)
                  return false;
              if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false)
                  return false;
      
              return true;
          }
      

      【讨论】:

        【解决方案9】:

        如果你使用CloudFlare缓存层服务,这里是修改版

        function getIP()
        {
            $fields = array('HTTP_X_FORWARDED_FOR',
                            'REMOTE_ADDR',
                            'HTTP_CF_CONNECTING_IP',
                            'HTTP_X_CLUSTER_CLIENT_IP');
        
            foreach($fields as $f)
            {
                $tries = $_SERVER[$f];
                if (empty($tries))
                    continue;
                $tries = explode(',',$tries);
                foreach($tries as $try)
                {
                    $r = filter_var($try,
                                    FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 |
                                    FILTER_FLAG_NO_PRIV_RANGE |
                                    FILTER_FLAG_NO_RES_RANGE);
        
                    if ($r !== false)
                    {
                        return $try;
                    }
                }
            }
            return false;
        }
        

        【讨论】:

          【解决方案10】:

          只是VB.NET 版本的答案:

          Private Function GetRequestIpAddress() As IPAddress
              Dim serverVariables = HttpContext.Current.Request.ServerVariables
              Dim headersKeysToCheck = {"HTTP_CLIENT_IP", _
                                        "HTTP_X_FORWARDED_FOR", _
                                        "HTTP_X_FORWARDED", _
                                        "HTTP_X_CLUSTER_CLIENT_IP", _
                                        "HTTP_FORWARDED_FOR", _
                                        "HTTP_FORWARDED", _
                                        "REMOTE_ADDR"}
              For Each thisHeaderKey In headersKeysToCheck
                  Dim thisValue = serverVariables.Item(thisHeaderKey)
                  If thisValue IsNot Nothing Then
                      Dim validAddress As IPAddress = Nothing
                      If IPAddress.TryParse(thisValue, validAddress) Then
                          Return validAddress
                      End If
                  End If
              Next
              Return Nothing
          End Function
          

          【讨论】:

          • 问题中有标签“PHP”
          【解决方案11】:

          另一种干净的方式:

            function validateIp($var_ip){
              $ip = trim($var_ip);
          
              return (!empty($ip) &&
                      $ip != '::1' &&
                      $ip != '127.0.0.1' &&
                      filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
                      ? $ip : false;
            }
          
            function getClientIp() {
              $ip = @$this->validateIp($_SERVER['HTTP_CLIENT_IP']) ?:
                    @$this->validateIp($_SERVER['HTTP_X_FORWARDED_FOR']) ?:
                    @$this->validateIp($_SERVER['HTTP_X_FORWARDED']) ?:
                    @$this->validateIp($_SERVER['HTTP_FORWARDED_FOR']) ?:
                    @$this->validateIp($_SERVER['HTTP_FORWARDED']) ?:
                    @$this->validateIp($_SERVER['REMOTE_ADDR']) ?:
                    'LOCAL OR UNKNOWN ACCESS';
          
              return $ip;
            }
          

          【讨论】:

            【解决方案12】:

            我意识到上面有更好更简洁的答案,这不是一个函数,也不是最优雅的脚本。在我们的例子中,我们需要在一个简单的开关中输出可欺骗的 x_forwarded_for 和更可靠的 remote_addr。它需要允许空白注入其他函数 if-none 或 if-singular(而不是仅仅返回预格式化的函数)。它需要一个“开或关”变量,并为平台设置提供每个交换机自定义标签。它还需要一种使 $ip 根据请求动态变化的方法,以便它采用 forwarded_for 的形式。

            我也没有看到任何人解决 isset() 与 !empty() 的问题——它可能不为 x_forwarded_for 输入任何内容,但仍然触发 isset() 真相导致空白 var,一种解决方法是使用 && 并将两者结合起来作为条件。请记住,您可以将诸如“PWNED”之类的词欺骗为 x_forwarded_for,因此如果您在受保护的地方或 DB 中输出,请确保您消毒为真正的 ip 语法。

            此外,如果您需要多代理来查看 x_forwarder_for 中的数组,您可以使用谷歌翻译进行测试。如果您想欺骗标头进行测试,请查看 Chrome Client Header Spoof 扩展名。在匿名代理之后,这将默认为标准 remote_addr。

            我不知道 remote_addr 可能为空的任何情况,但它作为备用以防万一。

            // proxybuster - attempts to un-hide originating IP if [reverse]proxy provides methods to do so
              $enableProxyBust = true;
            
            if (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR'])) && (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) && (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
                $ip = end(array_values(array_filter(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']))));
                $ipProxy = $_SERVER['REMOTE_ADDR'];
                $ipProxy_label = ' behind proxy ';
            } elseif (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR']))) {
                $ip = $_SERVER['REMOTE_ADDR'];
                $ipProxy = '';
                $ipProxy_label = ' no proxy ';
            } elseif (($enableProxyBust == false) && (isset($_SERVER['REMOTE_ADDR']))) {
                $ip = $_SERVER['REMOTE_ADDR'];
                $ipProxy = '';
                $ipProxy_label = '';
            } else {
                $ip = '';
                $ipProxy = '';
                $ipProxy_label = '';
            }
            

            要使这些动态用于下面的函数或查询/回显/视图,例如用于日志生成或错误报告,请使用全局变量或在任何您想要的地方回显它们,而无需制作大量其他条件或静态 -模式输出函数。

            function fooNow() {
                global $ip, $ipProxy, $ipProxy_label;
                // begin this actions such as log, error, query, or report
            }
            

            感谢您的所有伟大想法。请让我知道这是否可以更好,这些标题仍然有点新:)

            【讨论】:

              【解决方案13】:

              我想出了这个函数,它不仅返回 IP 地址,而且返回一个包含 IP 信息的数组。

              // Example usage:
              $info = ip_info();
              if ( $info->proxy ) {
                  echo 'Your IP is ' . $info->ip;
              } else {
                  echo 'Your IP is ' . $info->ip . ' and your proxy is ' . $info->proxy_ip;
              }
              

              函数如下:

              /**
               * Retrieves the best guess of the client's actual IP address.
               * Takes into account numerous HTTP proxy headers due to variations
               * in how different ISPs handle IP addresses in headers between hops.
               *
               * @since 1.1.3
               *
               * @return object {
               *         IP Address details
               *
               *         string $ip The users IP address (might be spoofed, if $proxy is true)
               *         bool $proxy True, if a proxy was detected
               *         string $proxy_id The proxy-server IP address
               * }
               */
              function ip_info() {
                  $result = (object) array(
                      'ip' => $_SERVER['REMOTE_ADDR'],
                      'proxy' => false,
                      'proxy_ip' => '',
                  );
              
                  /*
                   * This code tries to bypass a proxy and get the actual IP address of
                   * the visitor behind the proxy.
                   * Warning: These values might be spoofed!
                   */
                  $ip_fields = array(
                      'HTTP_CLIENT_IP',
                      'HTTP_X_FORWARDED_FOR',
                      'HTTP_X_FORWARDED',
                      'HTTP_X_CLUSTER_CLIENT_IP',
                      'HTTP_FORWARDED_FOR',
                      'HTTP_FORWARDED',
                      'REMOTE_ADDR',
                  );
                  foreach ( $ip_fields as $key ) {
                      if ( array_key_exists( $key, $_SERVER ) === true ) {
                          foreach ( explode( ',', $_SERVER[$key] ) as $ip ) {
                              $ip = trim( $ip );
              
                              if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
                                  $forwarded = $ip;
                                  break 2;
                              }
                          }
                      }
                  }
              
                  // If we found a different IP address then REMOTE_ADDR then it's a proxy!
                  if ( $forwarded != $result->ip ) {
                      $result->proxy = true;
                      $result->proxy_ip = $result->ip;
                      $result->ip = $forwarded;
                  }
              
                  return $result;
              }
              

              【讨论】:

                【解决方案14】:

                来自 Symfony 的 Request 类 https://github.com/symfony/symfony/blob/1bd125ec4a01220878b3dbc3ec3156b073996af9/src/Symfony/Component/HttpFoundation/Request.php

                const HEADER_FORWARDED = 'forwarded';
                const HEADER_CLIENT_IP = 'client_ip';
                const HEADER_CLIENT_HOST = 'client_host';
                const HEADER_CLIENT_PROTO = 'client_proto';
                const HEADER_CLIENT_PORT = 'client_port';
                
                /**
                 * Names for headers that can be trusted when
                 * using trusted proxies.
                 *
                 * The FORWARDED header is the standard as of rfc7239.
                 *
                 * The other headers are non-standard, but widely used
                 * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
                 */
                protected static $trustedHeaders = array(
                    self::HEADER_FORWARDED => 'FORWARDED',
                    self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
                    self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
                    self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
                    self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
                );
                
                /**
                 * Returns the client IP addresses.
                 *
                 * In the returned array the most trusted IP address is first, and the
                 * least trusted one last. The "real" client IP address is the last one,
                 * but this is also the least trusted one. Trusted proxies are stripped.
                 *
                 * Use this method carefully; you should use getClientIp() instead.
                 *
                 * @return array The client IP addresses
                 *
                 * @see getClientIp()
                 */
                public function getClientIps()
                {
                    $clientIps = array();
                    $ip = $this->server->get('REMOTE_ADDR');
                    if (!$this->isFromTrustedProxy()) {
                        return array($ip);
                    }
                    if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
                        $forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
                        preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
                        $clientIps = $matches[3];
                    } elseif (self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
                        $clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
                    }
                    $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
                    $firstTrustedIp = null;
                    foreach ($clientIps as $key => $clientIp) {
                        // Remove port (unfortunately, it does happen)
                        if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
                            $clientIps[$key] = $clientIp = $match[1];
                        }
                        if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
                            unset($clientIps[$key]);
                        }
                        if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
                            unset($clientIps[$key]);
                            // Fallback to this when the client IP falls into the range of trusted proxies
                            if (null ===  $firstTrustedIp) {
                                $firstTrustedIp = $clientIp;
                            }
                        }
                    }
                    // Now the IP chain contains only untrusted proxies and the client IP
                    return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
                }
                

                【讨论】:

                • 未定义属性:$server
                【解决方案15】:

                正如之前有人所说,这里的关键是您要存储用户的 ips 的原因。

                我将举一个我工作的注册系统的例子,当然还有解决方案,只是为了在我搜索中经常出现的这个旧讨论中做出贡献。

                许多 php 注册库使用 ip 来根据用户的 ip 限制/锁定失败的尝试。 考虑这张表:

                -- mysql
                DROP TABLE IF EXISTS `attempts`;
                CREATE TABLE `attempts` (
                  `id` int(11) NOT NULL AUTO_INCREMENT,
                  `ip` varchar(39) NOT NULL, /*<<=====*/
                  `expiredate` datetime NOT NULL,
                  PRIMARY KEY (`id`)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
                 -- sqlite
                ...
                

                然后,当用户尝试进行登录或与密码重置等服务相关的任何操作时,会在开始时调用一个函数:

                public function isBlocked() {
                      /*
                       * used one of the above methods to capture user's ip!!!
                       */
                      $ip = $this->ip;
                      // delete attempts from this ip with 'expiredate' in the past
                      $this->deleteAttempts($ip, false);
                      $query = $this->dbh->prepare("SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ?");
                      $query->execute(array($ip));
                      $attempts = $query->fetchColumn();
                      if ($attempts < intval($this->token->get('attempts_before_verify'))) {
                         return "allow";
                      }
                      if ($attempts < intval($this->token->get('attempts_before_ban'))) {
                         return "captcha";
                      }
                      return "block";
                   }
                

                例如,$this-&gt;token-&gt;get('attempts_before_ban') === 10 和 2 个用户使用相同的 ips,这与前面代码中的情况相同标头可以被欺骗,然后在 5 次尝试后,每个 两者都是禁止! 更糟糕的是,如果所有用户都来自同一个代理,那么只有前 10 个用户将被登录,其余用户将被禁止!

                这里的关键是我们需要表attempts 上的唯一索引,我们可以从以下组合中获得它:

                 `ip` varchar(39) NOT NULL,
                 `jwt_load varchar(100) NOT NULL
                

                其中jwt_load 来自遵循json web token 技术的http cookie,我们仅存储加密有效负载应该包含任意/唯一值用户。 当然请求应该修改为:"SELECT count(*) FROM {$this-&gt;token-&gt;get('table_attempts')} WHERE ip = ? AND jwt_load = ?",类也应该发起一个private $jwt

                【讨论】:

                  【解决方案16】:

                  我的答案基本上只是@AlixAxel 答案的完善、完全验证和完全打包的版本:

                  <?php
                  
                  /* Get the 'best known' client IP. */
                  
                  if (!function_exists('getClientIP'))
                      {
                          function getClientIP()
                              {
                                  if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) 
                                      {
                                          $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
                                      };
                  
                                  foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key)
                                      {
                                          if (array_key_exists($key, $_SERVER)) 
                                              {
                                                  foreach (explode(',', $_SERVER[$key]) as $ip)
                                                      {
                                                          $ip = trim($ip);
                  
                                                          if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
                                                              {
                                                                  return $ip;
                                                              };
                                                      };
                                              };
                                      };
                  
                                  return false;
                              };
                      };
                  
                  $best_known_ip = getClientIP();
                  
                  if(!empty($best_known_ip))
                      {
                          $ip = $clients_ip = $client_ip = $client_IP = $best_known_ip;
                      }
                  else
                      {
                          $ip = $clients_ip = $client_ip = $client_IP = $best_known_ip = '';
                      };
                  
                  ?>
                  

                  变化:

                  • 它简化了函数名称(带有'camelCase'格式样式)。

                  • 它包括一项检查,以确保该函数尚未在代码的另一部分中声明。

                  • 考虑到“CloudFlare”兼容性。

                  • 它将多个“IP 相关”变量名称初始化为“getClientIP”函数的返回值。

                  • 确保如果函数没有返回有效的 IP 地址,所有变量都设置为空字符串,而不是 null

                  • 只有 (45) 行代码。

                  【讨论】:

                    【解决方案17】:

                    我很惊讶没有人提到 filter_input,所以这里是 Alix Axel's answer 浓缩成一行:

                    function get_ip_address(&$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'])
                    {
                        return empty($keys) || ($ip = filter_input(INPUT_SERVER, array_pop($keys), FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))? $ip : get_ip_address($keys);
                    }
                    

                    【讨论】:

                      【解决方案18】:

                      我知道现在回答为时已晚。但您可以尝试以下选项:

                      选项 1:(使用 curl)

                      $ch = curl_init();
                      
                      // set URL and other appropriate options
                      curl_setopt($ch, CURLOPT_URL, "https://ifconfig.me/");
                      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                      
                      // grab URL and pass it to the browser
                      $ip = curl_exec($ch);
                      
                      // close cURL resource, and free up system resources
                      curl_close($ch);
                      
                      return $ip;
                      

                      选项 2:(在 mac 上运行良好)

                      return trim(shell_exec("dig +short myip.opendns.com @resolver1.opendns.com"));
                      

                      选项3:(只是使用了一个技巧)

                      return str_replace('Current IP CheckCurrent IP Address: ', '', strip_tags(file_get_contents('http://checkip.dyndns.com')));
                      

                      可能是参考: https://www.tecmint.com/find-linux-server-public-ip-address/

                      【讨论】:

                        【解决方案19】:

                        虽然这篇文章很老了,但这个话题仍然需要关注。所以在这里我提出了我在项目中使用的另一个解决方案。我发现这里的其他解决方案要么不完整,要么太复杂而无法理解。

                        if (! function_exists('get_visitor_IP'))
                        {
                            /**
                             * Get the real IP address from visitors proxy. e.g. Cloudflare
                             *
                             * @return string IP
                             */
                            function get_visitor_IP()
                            {
                                // Get real visitor IP behind CloudFlare network
                                if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
                                    $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
                                    $_SERVER['HTTP_CLIENT_IP'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
                                }
                        
                                // Sometimes the `HTTP_CLIENT_IP` can be used by proxy servers
                                $ip = @$_SERVER['HTTP_CLIENT_IP'];
                                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                                   return $ip;
                                }
                        
                                // Sometimes the `HTTP_X_FORWARDED_FOR` can contain more than IPs 
                                $forward_ips = @$_SERVER['HTTP_X_FORWARDED_FOR'];
                                if ($forward_ips) {
                                    $all_ips = explode(',', $forward_ips);
                        
                                    foreach ($all_ips as $ip) {
                                        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)){
                                            return $ip;
                                        }
                                    }
                                }
                        
                                return $_SERVER['REMOTE_ADDR'];
                            }
                        }
                        

                        【讨论】:

                          猜你喜欢
                          • 2018-09-25
                          • 2011-01-27
                          • 2018-10-29
                          • 1970-01-01
                          • 2012-08-14
                          • 1970-01-01
                          • 2018-03-19
                          相关资源
                          最近更新 更多