現狀
目前主流的函數方法:
<?php
function getIp()
{
if ($_SERVER["HTTP_CLIENT_IP"] && strcasecmp($_SERVER["HTTP_CLIENT_IP"], "unknown")) {
$ip = $_SERVER["HTTP_CLIENT_IP"];
} else {
if ($_SERVER["HTTP_X_FORWARDED_FOR"] && strcasecmp($_SERVER["HTTP_X_FORWARDED_FOR"], "unknown")) {
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
} else {
if ($_SERVER["REMOTE_ADDR"] && strcasecmp($_SERVER["REMOTE_ADDR"], "unknown")) {
$ip = $_SERVER["REMOTE_ADDR"];
} else {
if (isset ($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'],
"unknown")
) {
$ip = $_SERVER['REMOTE_ADDR'];
} else {
$ip = "unknown";
}
}
}
}
return ($ip);
}
echo getIp();
測試
curl偽造IP請求:
<?php
$ch = curl_init('http://localhost/ip.php');
//通用設置
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);//不直接輸出
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);//跟蹤重定向
//偽造請求頭
$ip = mt_rand(1, 255) . '.' . mt_rand(1, 255) . '.' . mt_rand(1, 255) . '.' . mt_rand(1, 255);
$header = [
'CLIENT-IP: ' . $ip,
'X-FORWARDED-FOR: ' . $ip,
'X-REAL-IP: ' . $ip,
'Accept-Language: zh-CN,zh;',
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
$html = curl_exec($ch);
curl_close($ch);
echo $html;
輸出SERVER數組,發現【HTTP_CLIENT_IP】、【HTTP_X_FORWARDED_FOR】、【HTTP_X_REAL_IP】是隨機變動的IP地址。
主流方法根本不安全!
分析
為什么?
HTTP_CLIENT_IP:存在於http請求的header
HTTP_X_FORWARDED_FOR:請求轉發路徑,客戶端IP,代理1IP,代理2IP......
HTTP_X_REAL_IP:這個用得比較少,暫不討論。
這三個值都是從HTTP請求頭獲取的,所以並不可靠!
REMOTE_ADDR:是直接從TCP中獲取的IP,基本不會被偽造!
返回查看$_SERVER數組,發現【REMOTE_ADDR】顯示正確的IP!
所以直接用 $_SERVER['REMOTE_ADDR'] 就解決問題了?
其實還不行,如果客戶端和服務器之間存在代理服務器,【REMOTE_ADDR】的值是最后一個代理服務器的IP!
只有第一台接收客戶端請求的代理服務器的【REMOTE_ADDR】值才是客戶的真實IP地址,要把該值傳遞下去!
解決方案
1. 客戶端和服務器直連
<?php
function get_client_ip()
{
$ip = $_SERVER['REMOTE_ADDR'];
return $ip;
}
2. 客戶端和服務器存在中間代理
第一層nginx代理設置:
proxy_set_header X-Forwarded-For $remote_addr;
其他層nginx代理設置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
PHP代碼:
<?php
function get_client_ip()
{
$ip = null;
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = trim(current($ip));
}
return $ip;
}