thinkphp5如何获得访客IP?nginx传递ip的注意事项
发布于 作者:苏南大叔 来源:程序如此灵动~本文也是一篇水文,不过苏南大叔会稍稍给它写出一些深度来。文章中首先要说明一下thinkphp
中获得ip
的方法,然后描述一下通用的获得ip
的方法及其漏洞,最后描述一下nginx
和php
配合使用的时候,获取ip
的注意事项。
本文测试环境:mac
/php72
/nginx
/thinkphp@5.0.24
。
thinkphp5
获得访客IP
thinkphp5
自带的获得ip
的代码,非常简单:
首先引入Request
类包:
use think\Request;
然后初始化Request
实例后,调用ip
方法获得ip
。
$request = Request::instance();
$ip=$request->ip();
源文件的位置在thinkphp\library\think\Request.php
:
public function ip($type = 0, $adv = true)
{
$type = $type ? 1 : 0;
static $ip = null;
if (null !== $ip) {
return $ip[$type];
}
$httpAgentIp = Config::get('http_agent_ip');
if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) {
$ip = $_SERVER[$httpAgentIp];
} elseif ($adv) {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown', $arr);
if (false !== $pos) {
unset($arr[$pos]);
}
$ip = trim(current($arr));
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u", ip2long($ip));
$ip = $long ? [$ip, $long] : ['0.0.0.0', 0];
return $ip[$type];
}
源码的逻辑内容,大家自行理解。不过值得注意的是:上面这段thinkphp
的代码,可以获取隐藏在自定义header
头中的ip
信息,这个思路看起来是很新颖不错,点赞。
thinkphp3系列中,获得ip
就一个函数,更加简单,get_client_ip()
。
网络流传的获取ip
的函数
下面的php
代码,是网络流传的获得ip
的php
函数。大家仔细看逻辑,就可以看到相关的漏洞所在。
function get_ip(){
if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
$cip = $_SERVER["HTTP_CLIENT_IP"];
} elseif (!empty($_SERVER["HTTP_X_FORWARDED_FOR"])) {
$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
} elseif (!empty($_SERVER["REMOTE_ADDR"])) {
$cip = $_SERVER["REMOTE_ADDR"];
} else {
$cip = "--";
}
return $cip;
}
大家可以看到,这个ip
的获取,是有else
和if
的,而其中的HTTP_X_FORWARDED_FOR
,这个就是漏洞所在。如果相关的脚本,是去先检测HTTP_X_FORWARDED_FOR
的话,那么就有一定的概率可以伪造ip
了。这里,就涉及到匿名代理和透明代码的相关知识了,这里不做赘述。
thinkphp@3
系列中的get_client_ip()
中,也考虑到上述问题。下面是其定义:
/**
* 获取客户端IP地址
* @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
* @param boolean $adv 是否进行高级模式获取(有可能被伪装)
* @return mixed
*/
function get_client_ip($type = 0,$adv=false) {
$type = $type ? 1 : 0;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if($adv){
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown',$arr);
if(false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
}elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u",ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}
配合nginx
使用
众所周知,大多数时候,线上环境下,都是配合nginx
来使用php
的。而nginx
对于php
来说,就是个代理。所以,如果配置不当的情况下,就会恒定取到本机ip
作为客户ip
,这个就是很令人崩溃的事情了。
不过,这种情况,就需要nginx
和php
共同协商决定ip
的判断标准了。
nginx
配置ip
:
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这里的X-Real-IP
,就是目标header
。
php
获得ip
:
if (isset($_SERVER[`X-Real-IP`])) {
$ip = $_SERVER[`X-Real-IP`];
}
总结
本文中,苏南大叔并没有推荐任何一种获取ip
的方式,大家各自根据实际情况做出选择吧。
ip
对于个人来说,就是一串数字罢了。但是在现实的网络环境中,ip
是个集合,它包含了各个层面上的代理的结果。所以,对于获得ip
这件事情来说,还是仔细思量一下比较好。因为ip
的判断标准过于多样,那么这些取ip
的逻辑,基本上都可能会存在着些漏洞。所以,使用这些函数方法前,请仔细想清楚。
下面的链接,是苏南大叔写的有关thinkphp
的相关文章,敬请惠存:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。