使用PHP通过代理获取IP地址?

6

我会对多个AJAX调用进行安全检查,以查看是否有相同的IP请求了我记录中的IP。

我使用以下一组类函数来确定IP(可能通过负载均衡器获取,因此需要冗长的方法)。

    private function IPMask_Match ($network, $ip) {
      $ip_arr = explode('/', $network);
      if (count($ip_arr) < 2) {
        $ip_arr = array($ip_arr[0], null);
      }
      $network_long = ip2long($ip_arr[0]);
      $x = ip2long($ip_arr[1]);
      $mask =  long2ip($x) == $ip_arr[1] ? $x : 0xffffffff << (32 - $ip_arr[1]);
      $ip_long = ip2long($ip);
      return ($ip_long & $mask) == ($network_long & $mask);
    }


    private function IPCheck_RFC1918 ($IP) {
      $PrivateIP = false;
      if (!$PrivateIP) {
        $PrivateIP = $this->IPMask_Match('127.0.0.0/8', $IP);
      }
      if (!$PrivateIP) {
        $PrivateIP = $this->IPMask_Match('10.0.0.0/8', $IP);
      }
      if (!$PrivateIP) {
        $PrivateIP = $this->IPMask_Match('172.16.0.0/12', $IP);
      }
      if (!$PrivateIP) {
        $PrivateIP = $this->IPMask_Match('192.168.0.0/16', $IP);
      }
      return $PrivateIP;
    }


    public function getIP () {
      $UsesProxy = (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) || !empty($_SERVER['HTTP_CLIENT_IP'])) ? true : false;
      if ($UsesProxy && !empty($_SERVER['HTTP_CLIENT_IP'])) {
        $UserIP = $_SERVER['HTTP_CLIENT_IP'];
      }
      elseif ($UsesProxy && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $UserIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
        if (strstr($UserIP, ',')) {
          $UserIPArray = explode(',', $UserIP);
          foreach ($UserIPArray as $IPtoCheck) {
            if (!$this->IPCheck_RFC1918($IPtoCheck)) {
              $UserIP = $IPtoCheck;
              break;
            }
          }
          if ($UserIP == $_SERVER['HTTP_X_FORWARDED_FOR']) {
            $UserIP = $_SERVER['REMOTE_ADDR'];
          }
        }
      }
      else{
        $UserIP = $_SERVER['REMOTE_ADDR'];
      }
      return $UserIP;
    }

问题是我一直遇到用户通过代理操作时出现问题。有人能指出可能的原因吗?我已经使用免费基本代理在线尝试模拟,但似乎没有获取可变IP或其他任何信息-所以我不确定为什么会说两个IP不匹配。


如果$_SERVER['HTTP_X_FORWARDED_FOR']中没有逗号,您的代码会发生什么?我不理解这部分代码:if ($UserIP == $_SERVER['HTTP_X_FORWARDED_FOR']) { $UserIP = $_SERVER['REMOTE_ADDR']; } - Jan Prieser
好问题 - 如果没有,则只需将其保留为HTTP_X_FORWARDED_FOR。第二个问题是说,如果没有转发IP(在负载均衡器上获取),则使用REMOTE_ADDR - 这是最基本的IP。 - waxical
5个回答

2

首先,我将解释什么是代理,以便我们能够达成共识。

代理是什么

代理通常是一台计算机,代表用户访问互联网,然后代理将结果发送回用户。问题出现在可能有数百甚至数千其他人也在使用同一台计算机,但他们都有相同的IP地址,但通常标头表明用户通过代理访问。

我假设您的脚本(没有正确查看)混淆了IP和标头。

不同的解决方案

有更好的方法。使用会话并在会话中保存密钥,确保他们先前已经访问了主站点,然后再访问ajax页面。例如:

index.php

session_start();
$_SESSION['ajax_ok'] = true;

ajax/username_check.php

session_start();
if (empty($_SESSION['ajax_ok'])) {
    die("You can not access this page...");
}

这将强制客户端必须先访问您的主站点,并且该客户端还必须支持会话,大多数浏览器都支持这一点,有助于防止机器人等滥用您的ajax脚本。

与使用上述混乱代码相比,更可靠且更容易的解决方案。

如果无法使用会话怎么办?

如果您所在的计算机无法使用会话,则可以设置另一台计算机,重写会话处理程序(使用PHP提供的回调函数),或者不使用文件系统,而是使用其他东西(例如数据库)来代替会话?您的网站肯定有一些内容也可以用于会话,例如如果您有一个负载均衡器,则基于文件的会话通常无法正常工作,因此最好将会话更改为使用我上面提到的其他方法。


我认为这是否是更好的方法还有争议。当然,这是另一种方式。有时候我无法使用会话,所以采用了更广泛的非会话方法。感谢您的建议,如果没有其他更好的选择,我可能不得不采取这种方法。我认为有人给你投了反对票,因为它实际上并没有回答问题,但我已经给你点赞了。 - waxical
谢谢,你可能是对的,他们为什么会给我投反对票,但我认为没有人直接回答你的问题是因为很难在没有可调试的配置设置的情况下回答。这就是为什么我给了你一个小的解决方法。 - HenchHacker
1
如果您无法在特定计算机上使用会话,您可以设置另一台计算机,重写会话处理程序(使用PHP提供的回调函数),或者不使用文件系统,而改用数据库来代替会话?例如,如果您有一个负载均衡器,则基于文件的会话通常无法正常工作。 - HenchHacker

2
很遗憾,问题几乎肯定不是代理 - 几乎肯定是一个固定的公共IP路由器,通过子网路由流量。
子网可以非常巨大(比如在大学里)。
即使它是一个真正的代理(这些技术已经有10年了,很少见),即使代理自愿转发,也不会有任何意义,因为它几乎肯定是类似于192.168.x.x的子网ip。这基本上就是公共IP地址(也称交换机)的内部扩展。
您可以交叉手指尝试php ipv6 Working with IPv6 Addresses in PHP,甚至更聪明地尝试mac地址How can I get the MAC and the IP address of a connected client in PHP?,但两者都注定会失败。我的直觉是要尝试欺骗:我会赌最好的方法基本上是使用网络共享进行会话存储,并允许负载平衡的PHP服务器都可以访问它,并通过相同的DNS前缀执行所有操作。或者设置第三方DNS来进行会话收集。
答案是,除非您像广告代理一样跟踪“cookie”,否则您无法做到这一点。

Cookie似乎是唯一真正的解决方案,如果你确实需要“确定”你正在与谁交流。它应该比IP更容易管理。除非有人偷了cookie,否则你知道它是之前的同一台设备。无论如何,基于IP或cookie的安全性仍然非常轻。我不知道你想要实现什么,但康纳斯是正确的,cookie似乎是最好的解决方案。 - Perello

1
一个朋友指出了这个问题,基本上一些代理服务器返回的X_FORWARDED_FOR可能是逗号分隔的值或者是逗号和空格分隔的值。
解决方法:在此之后:
foreach ($UserIPArray as $IPtoCheck) {

添加这行:

$IPtoCheck = trim($IPtoCheck);

已排序。


0
问题是我一直遇到用户通过代理操作的问题。有人能指出可能的原因吗?我已经使用基本的免费在线代理来尝试模拟,但似乎没有得到可变的IP或任何东西-所以我不确定为什么会说这两个IP不匹配。
你的解析代码在逗号上爆炸 HTTP_X_FORWARDED_FOR,但分隔符可能是“逗号空格”。如果发生这种情况,RFC 1918检查将失败。虽然某些代理不添加空格,但标准是使用它:
http://en.wikipedia.org/wiki/X-Forwarded-For

The general format of the field is:

X-Forwarded-For: client, proxy1, proxy2

where the value is a comma+space separated list of IP addresses, the left-most being the original client, and each successive proxy that passed the request adding the IP address where it received the request from. In this example, the request passed proxy1, proxy2 and proxy3 (proxy3 appears as remote address of the request).

所以你应该将explode的分隔符改为", ",或者更好的方法是使用preg_split作为分隔符,并覆盖两种情况。

然后,你的问题是如何验证调用AJAX的页面和AJAX调用本身。

如果你不想使用基于cookie的会话,这是最好的方法,你可以尝试使用一个nonce。也就是说,当你生成页面时,你发出一个唯一的ID并将其注入到HTML代码中,在那里AJAX代码将恢复它并传回到AJAX servlet。后者将能够将其添加到Access-Control-Request中,详见此处,或者只需在请求中添加更多数据。


用逗号分隔后再进行trim()处理是可以的。或许可以使用array_walk()函数来实现这个功能。 - Sven
是的,在我看来,最好的方法仍然是使用preg_split,它可以双向工作,而且不需要trim。实际上,我根本不会依赖头文件;会话仍然是最干净的方式。但如果不能使用它,那么preg_split提供了一个干净的分割接口。 - LSerni

0

我想讨论一下你的解决方案是否有助于安全。

$_SERVER['REMOTE_ADDR'] 无法伪造。它是由 Web 服务器设置的,因为使用了访问 IP 地址。任何响应都将发送到此地址。

$_SERVER['HTTP_FORWARDED_FOR'] 和 $_SERVER['HTTP_CLIENT_IP'] 可以很容易地被伪造,因为它们是发送到 Web 服务器的 HTTP 标头 - 如果代理配置为省略这些标头,则您不会知道您正在与代理通信,如果客户端决定插入这些标头,则您也不会知道您没有与代理通信。

基于伪造的 IP 地址进行过滤并不能真正帮助您,但这在很大程度上取决于您想要实现的内容 - 直到您更详细地了解之前,这仍然是未知的。

如果您四处寻找,您会发现 PHP 的 Suhosin 补丁和扩展,以及其加密会话和 cookie 内容的功能。加密密钥是通过使用一些静态密钥构建的,但添加了一些东西,例如 HTTP 用户代理或请求 IP 地址的部分 - 注意:实际用于发出请求的 IP,即如果使用代理,则为代理,因为这是唯一可靠的信息。

有人认为使用完整的IP地址并不是一个很好的主意,除非你知道你的用户不使用代理集群来进行多次请求并更改IP地址,因此通常只会使用IP的一部分或根本不使用。

然而,HTTP用户代理是一个很好的唯一信息来源。是的,它可以被伪造,但这一点并不重要,因为如果您只想允许来自同一来源的请求,那么假设用户代理字符串随时间不会变化,但对于其他用户来说将会是不同的是有效的。因此,有统计数据表明,如果您仅查看发送的所有HTTP标头,就可以生成几乎唯一的浏览器指纹,因为每个用户都安装了不同的扩展程序来更改某些内容,例如接受内容标头。

我无法提供工作代码,也无法从您的问题中确定任何答案是否适用,但我建议不要使用IP信息,特别是如果它可以被伪造。如果考虑到IPv6,这一点甚至更为重要,因为即使在同一界面上,每个客户端也有多个活动地址,并且它们都是随机生成的,极不可能在以后的时间内再次出现。(当然,如果您永远不打算在IPv6上托管,则不适用此规则,但在某个时候您将会失去用户。)


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