我知道有很多 $_SERVER 变量头可用于IP地址检索 . 我想知道如何使用所述变量最准确地检索用户的真实IP地址(很清楚没有方法是完美的)?
我花了一些时间试图找到一个深入的解决方案,并根据许多来源提出了以下代码 . 我会喜欢它,如果有人可以请求答案中的漏洞或对某些可能更准确的东西有所了解 .
edit includes optimizations from @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地址结合使用 .
For 99.9% of users this solution will suit your needs perfectly. 它不会通过注入自己的请求标头来保护您免受0.1%的恶意用户的攻击 . 如果依赖IP地址来完成关键任务,请求 REMOTE_ADDR
并且不要为代理背后的人提供服务 .
16 回答
我想知道你是否应该以相反的顺序迭代爆炸的HTTP_X_FORWARDED_FOR,因为我的经验是用户的IP地址最终在逗号分隔列表的末尾,所以从头的开头开始,你是更有可能获得返回的其中一个代理的IP地址,这可能仍然允许会话劫持,因为许多用户可能通过该代理 .
Here is a shorter, cleaner way to get the IP address:
我希望它有所帮助!
你的代码似乎已经非常完整,我看不到任何可能的错误(除了通常的IP警告),我会改变
validate_ip()
函数依赖于过滤器扩展:您的
HTTP_X_FORWARDED_FOR
代码段也可以简化:对此:
您可能还需要验证IPv6地址 .
然而,即便如此,获取用户的真实IP地址也是不可靠的 . 他们所需要做的就是使用一个匿名代理服务器(一个不尊重http_x_forwarded_for,http_forwarded等标头的服务器),你得到的只是他们的代理服务器的IP地址 .
然后,您可以查看是否存在匿名的代理服务器IP地址列表,但是无法确保100%准确,并且它所做的最多就是让您知道它是代理服务器 . 如果有人聪明,他们可以欺骗HTTP转发的标头 .
假设我不喜欢当地的大学 . 我弄清楚他们注册了哪些IP地址,并通过做坏事来获取他们在您网站上禁用的IP地址,因为我发现您尊重HTTP转发 . 名单是无止境的 .
然后,正如您所猜测的那样,内部IP地址,例如我之前提到过的大学网络 . 很多都使用10.x.x.x格式 . 所以你要知道的是,它被转发为共享网络 .
然后我不会开始考虑它,但动态IP地址已经成为宽带的方式了 . 所以 . 即使您获得了用户IP地址,也希望它在2-3个月内更改,最长 .
我们用:
HTTP_X_FORWARDED_FOR的爆炸是因为我们在使用Squid时检测到IP地址的奇怪问题 .
最大的问题是出于什么目的?
您的代码几乎尽可能全面 - 但我看到如果您发现看起来像代理添加 Headers 的内容,您可以使用CLIENT_IP的INSTEAD,但是如果您希望将此信息用于审计目的,则会发出警告 - 非常简单假的 .
当然,你永远不应该使用IP地址进行任何形式的身份验证 - 即使这些也可能是欺骗性的 .
你可以通过推出一个通过非http端口连接回服务器的flash或java applet来更好地测量客户端ip地址(因此会显示透明代理或代理注入的头部为假的情况 - 但是请记住,客户端只能通过Web代理进行连接或者传出端口被阻止,小程序将无法连接 .
C .
只是VB.NET版本的答案:
我意识到上面有更好,更简洁的答案,这不是一个功能,也不是最优雅的脚本 . 在我们的例子中,我们需要在简单的交换机中输出可欺骗的x_forwarded_for和更可靠的remote_addr . 它需要允许空格注入其他函数if-none或if-singular(而不仅仅是返回预格式化的函数) . 它需要一个“开启或关闭”变量,每个开关定制标签用于平台设置 . 它还需要一种方法让$ ip动态,具体取决于请求,以便它采取forwarding_for的形式 .
此外,我没有看到任何人地址isset()vs!empty() - 它可能为x_forwarded_for输入任何内容但仍然触发isset()真相导致空白var,一种解决方法是使用&&并将两者结合起来作为条件 . 请记住,您可以将“PWNED”这样的单词伪装为x_forwarded_for,因此如果输出受保护的内容或进入数据库,请确保对真正的ip语法进行消毒 .
此外,您可以使用谷歌翻译测试if你需要一个多代理来查看x_forwarder_for中的数组 . 如果你想欺骗 Headers 进行测试,请查看Chrome Client Header Spoof扩展名 . 这将默认为在anon代理后面的标准remote_addr .
我不知道remote_addr可能是空的任何情况,但它的后备以防万一 .
要使这些动态在下面的函数或查询/回显/视图中使用,例如对于日志或错误报告,请使用全局变量或只在所需的任何地方回显em而不需要大量其他条件或静态模式输出功能 .
谢谢你所有的好主意 . 如果这可能会更好,请让我知道,这些 Headers 仍然有点新:)
我想出了这个函数,它不仅仅返回IP地址,而是一个包含IP信息的数组 .
这是功能:
我的答案基本上只是@ AlixAxel答案的完美,完全验证和完全打包的版本:
变化:
它简化了函数名称(使用'camelCase'格式化样式) .
它包括一个检查,以确保该函数尚未在代码的另一部分中声明 .
它考虑了“CloudFlare”的兼容性 .
它将多个“IP相关”变量名称初始化为'getClientIP'函数的返回值 .
它确保如果函数没有返回有效的IP地址,则所有变量都设置为空字符串,而不是
null
.这只是(45)行代码 .
谢谢你,非常有用 .
如果代码在语法上是正确的,那将有所帮助 . 因为它有一个{太多了20行 . 我害怕意味着没有人真正试过这个 .
我可能很疯狂,但在尝试了几个有效和无效的地址之后,唯一有效的validate_ip()版本就是:
如果您使用CloudFlare缓存层服务,这是一个修改后的版本
另一种干净的方式:
来自Symfony的Request类https://github.com/symfony/symfony/blob/1bd125ec4a01220878b3dbc3ec3156b073996af9/src/Symfony/Component/HttpFoundation/Request.php
正如之前有人所说,这里的关键是你想要存储用户的ips的原因 .
我将从我工作的注册系统中给出一个例子,当然这个解决方案只是为了在我的搜索中频繁出现的旧讨论中做出贡献 .
许多php注册库使用 ip 根据用户的ip来限制/锁定失败的尝试 . 考虑一下这个表:
然后,当用户尝试进行登录或与密码重置等服务相关的任何事情时,会在开始时调用一个函数:
比方说,例如,
$this->token->get('attempts_before_ban') === 10
和2个用户使用相同的ips,就像之前的代码 where headers can be spoofed 中的情况一样,然后每次尝试5次 both are banned !即使最糟糕的是,如果所有来自同一个代理,那么只会记录前10个用户,其余的将被禁止!这里的关键是我们需要一个表
attempts
上的唯一索引,我们可以从以下组合中得到它:其中
jwt_load
来自遵循json web token技术的http cookie,其中我们只存储 encrypted 有效负载, should 包含每个用户的任意/唯一值 . 当然应该将请求修改为:"SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ? AND jwt_load = ?"
并且该类也应该启动private $jwt
.你几乎回答了自己的问题! :)
Source