在無法調整伺服器防火牆 (iptables、Nginx deny、.htaccess) 的虛擬主機環境中,透過 PHP 實作 IP 黑名單是快速有效的備選方案。本文提供單一檔案部署、支援單一 IP 與 CIDR 區段封鎖的完整實作。
在需保護的 PHP 檔案最頂端引入:
<?php require_once 'banIP.php'; // 你的業務邏輯... ?>
<?php
/**
* IP 封鎖檢查器
* 支援:單一 IPv4、IPv6、CIDR 區段 (如 192.168.1.0/24)
* 部署:將封鎖清單存於 ban.dat (每行一條),或直接寫在陣列中
*/
class IpBanChecker {
private $banListFile = __DIR__ . '/ban.dat';
private $banRules = [];
private $clientIp;
public function __construct() {
$this->clientIp = $this->getClientIp();
$this->loadRules();
$this->check();
}
// 取得客戶端真實 IP (考慮代理)
private function getClientIp(): string {
$headers = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
foreach ($ips as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
// 載入封鎖規則
private function loadRules(): void {
// 1. 從檔案讀取 (每行一條,支援 # 註解)
if (is_file($this->banListFile)) {
$lines = file($this->banListFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
$this->banRules[] = $line;
}
}
// 2. 也可直接在這裡硬編碼規則
// $this->banRules[] = '192.168.1.100';
// $this->banRules[] = '10.0.0.0/8';
}
// 執行檢查
private function check(): void {
foreach ($this->banRules as $rule) {
if ($this->matchRule($this->clientIp, $rule)) {
$this->denyAccess($rule);
}
}
}
// IP 比對邏輯 (支援 CIDR)
private function matchRule(string $ip, string $rule): bool {
if (strpos($rule, '/') !== false) {
// CIDR 區段比對
return $this->matchCIDR($ip, $rule);
}
// 單一 IP 精確比對
return $ip === $rule;
}
// CIDR 比對核心
private function matchCIDR(string $ip, string $cidr): bool {
list($subnet, $mask) = explode('/', $cidr);
$mask = (int)$mask;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
// IPv4
$ipLong = ip2long($ip);
$subnetLong = ip2long($subnet);
if ($ipLong === false || $subnetLong === false) return false;
$maskLong = -1 << (32 - $mask);
return ($ipLong & $maskLong) === ($subnetLong & $maskLong);
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// IPv6 (簡易版,生產環境建議用專用庫)
// 這裡略過完整 IPv6 CIDR 實作,建議引用 league/ip 或 symfony/polyfill
return false;
}
return false;
}
// 拒絕存取回應
private function denyAccess(string $matchedRule): void {
header('HTTP/1.1 403 Forbidden');
header('Content-Type: text/html; charset=utf-8');
echo "403 Forbidden
";
echo "您的 IP ({$this->clientIp}) 遭到封鎖。
";
echo "匹配規則:{$matchedRule}
";
exit;
}
}
// 自動執行檢查
new IpBanChecker();
?>
# 單一 IPv4 192.168.1.100 119.184.251.245 # IPv4 CIDR 區段 192.168.1.0/24 10.0.0.0/8 172.16.0.0/12 # IPv6 (需擴充 matchCIDR 完整支援) # 2001:db8::/32 # 註解行以 # 開頭
[$ipLong, $maskLong] 數值陣列,比對時僅做位元運算。deny 或防火牆層級攔截。| 方案 | 優點 | 缺點 | 推薦場景 |
|---|---|---|---|
| PHP 應用層封鎖 | 部署簡單、無需伺服器權限、邏輯靈活 | 效能較差、需 PHP 執行才能攔截 | 虛擬主機、快速上線、邏輯複雜 |
| Nginx deny / allow | 效能極佳、早期攔截、不佔用 PHP 進程 | 需伺服器管理權限、配置重載生效 | VPS/獨立伺服器、高流量 |
| iptables / firewalld | 核心層攔截、最徹底 | 需 root、操作風險高 | 系統級防護、DDoS 緩解 |
| WAF (ModSecurity/Cloudflare) | 規則豐富、防注入/爬蟲/CC | 成本高、誤攔率 | 企業級、高安全需求 |