| 來源 | 取得方式 | 可信度 | 適用場景 |
|---|---|---|---|
HTTP_X_FORWARDED_FOR | HTTP 標頭 X-Forwarded-For | 低(可偽造) | 負載均衡、代理後端 |
HTTP_CLIENT_IP | HTTP 標頭 Client-Ip | 低(可偽造) | 極少使用,不推薦 |
REMOTE_ADDR | TCP 連線來源 IP | 高(難偽造) | 直接連線、無代理環境 |
X-Forwarded-For: client, proxy1, proxy2
function getRealIp(array $trustedProxies = []): string {
// 1. 直接連線環境
if (empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['REMOTE_ADDR'];
}
// 2. 解析 XFF 鏈
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'];
$ips = array_map('trim', explode(',', $xff));
// 3. 從右向左找第一個不在信任代理名單中的 IP
$trusted = array_flip($trustedProxies); // O(1) 查找
for ($i = count($ips) - 1; $i >= 0; $i--) {
if (!isset($trusted[$ips[$i]])) {
return $ips[$i];
}
}
// 4. 全鏈路皆為信任代理(理論上不應發生)
return $ips[0] ?? $_SERVER['REMOTE_ADDR'];
}
// 使用範例
$trustedProxies = [
'10.0.0.1', '10.0.0.2', // 內部負載均衡
'192.168.1.100', // 反向代理
// 雲端服務商 IP 範圍需查閱官方文檔
];
$realIp = getRealIp($trustedProxies);
CF-Connecting-IP 標頭更可靠,IP 範圍見 官網X-Forwarded-For 最後一個非 GCP IPX-Forwarded-For 格式標準,信任 100.64.0.0/10// 攻擊者偽造標頭
$context = stream_context_create([
'http' => [
'header' => "X-Forwarded-For: 1.2.3.4\r\n" .
"Client-Ip: 5.6.7.8\r\n" .
"User-Agent: Mozilla/5.0...\r\n"
]
]);
file_get_contents('https://target.com/vote', false, $context);
Via、X-Proxy-ID、開放代理掃描class VoteService {
private Redis $redis;
private array $trustedProxies;
public function vote(string $itemId, string $choice): bool {
$ip = $this->getRealIp();
$deviceId = $this->getDeviceFingerprint();
// 1. 速率限制:IP 維度
$ipKey = "vote:rate:ip:$ip";
if ($this->redis->incr($ipKey) > 30) return false; // 30/分鐘
$this->redis->expire($ipKey, 60);
// 2. 速率限制:裝置維度
$devKey = "vote:rate:dev:$deviceId";
if ($this->redis->incr($devKey) > 10) return false; // 10/分鐘
$this->redis->expire($devKey, 60);
// 3. 防重複:業務鍵
$voteKey = "vote:done:$itemId:$ip:$deviceId";
if ($this->redis->exists($voteKey)) return false;
// 4. 寫入投票(原子操作)
$pipe = $this->redis->pipeline();
$pipe->incr("vote:count:$itemId:$choice");
$pipe->set($voteKey, 1, 'EX', 86400 * 30); // 30 天有效
$pipe->exec();
return true;
}
private function getDeviceFingerprint(): string {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
$lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
return substr(md5($ua . $accept . $lang), 0, 16);
}
private function getRealIp(): string {
// ... 實作同上 ...
}
}
itemId + ip + device,而非單純 IP理解 IP 獲取的本質與局限,建立多層次防禦體系,才能在刷票、薅羊毛、惡意註冊等對抗中立於不敗之地。