Реализация проверки домена в PHP для ограничения доступа к ресурсам
Блокировка домена в PHP: защита от несанкционированных запросов
Основное эффективное решение: проверка заголовка Origin с белым списком доменов
Наиболее надёжный способ в современных веб-приложениях - использовать HTTP-заголовок Origin, который отправляется браузерами при кросс-доменных запросах (CORS) и не может быть подделан скриптами (в отличие от Referer). Реализация включает проверку присутствия заголовка и его соответствия разрешённым доменам.
// Белый список разрешённых доменов (без www)
$allowedOrigins = [
'https://mysite.com',
'https://api.mysite.com'
];
if (isset($_SERVER['HTTP_ORIGIN'])) {
$origin = $_SERVER['HTTP_ORIGIN'];
if (!in_array($origin, $allowedOrigins)) {
http_response_code(403);
echo json_encode(['error' => 'Доступ запрещён: недопустимый домен']);
exit;
}
} else {
// Если Origin отсутствует, можно разрешить (для прямых GET-запросов) или заблокировать
// Для критичных операций лучше запросить Origin или отказать
http_response_code(400);
echo json_encode(['error' => 'Отсутствует заголовок Origin']);
exit;
}
Данный код завершает выполнение с кодом 403 при несовпадении домена. Для работы с поддоменами можно использовать регулярное выражение.
// Проверка с учётом поддоменов
$allowedDomain = 'mysite.com';
$pattern = '/^https?:\/\/([a-z0-9-]+\.)*' . preg_quote($allowedDomain, '/') . '$/i';
if (!preg_match($pattern, $origin)) {
http_response_code(403);
exit('Forbidden');
}
Возможные проблемы и ошибки:
- Заголовок Origin может отсутствовать при прямых запросах (например, из curl без соответствующей настройки). Решение: проверять наличие и, если нужно, требовать его.
- Некоторые прокси-серверы могут модифицировать Origin. Рекомендуется проверять на уровне приложения.
- При использовании поддоменов обязательно экранировать точку в регулярном выражении.
Цель использования: защита API или критических действий от вызова с посторонних сайтов (CSRF-атаки, hotlinking).
Как заблокировать запросы на основе заголовка Referer?
Традиционный, но менее надёжный метод - проверка HTTP_REFERER. Referer легко подделывается, но всё ещё используется для базовой фильтрации.
$allowedReferer = 'https://mysite.com/';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (strpos($referer, $allowedReferer) !== 0) {
http_response_code(403);
exit('Access denied');
}
Проблема: Referer может отсутствовать (политика конфиденциальности), быть изменён расширениями браузера или скриптами. Не рекомендуется для критичных операций.
Как ограничить доступ к приложению по домену через .htaccess?
На уровне веб-сервера Apache можно блокировать запросы по Referer, не затрагивая код PHP.
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?mysite\.com/ [NC]
RewriteRule .* - [F]
Referer всё так же ненадёжен. Кроме того, блокировка коснётся всех типов запросов, что может нарушить работу легитимных инструментов (например, поисковых роботов).
Как проверить домен по IP-адресу клиента (PTR-запись)?
С помощью функции gethostbyaddr можно получить доменное имя клиента и сравнить его с белым списком.
$ip = $_SERVER['REMOTE_ADDR'];
$hostname = gethostbyaddr($ip);
if (strpos($hostname, 'trustednetwork.com') === false) {
http_response_code(403);
exit('Forbidden');
}
Этот метод ненадёжен: не все IP имеют обратную запись, запись может быть подделана, функция выполняется медленно (DNS-запрос). Подходит только для внутренних сетей или при дополнительной проверке.
Как реализовать блокировку домена через проверку Host?
Если приложение обслуживает несколько доменов, можно запретить доступ с нежелательных.
$allowedHosts = ['mysite.com', 'www.mysite.com'];
$host = parse_url($_SERVER['HTTP_HOST'] ?? '', PHP_URL_HOST);
if (!in_array($host, $allowedHosts)) {
http_response_code(403);
exit();
}
Заголовок Host можно подделать при прямых TCP-соединениях. Метод полезен только для виртуальных хостов в связке с конфигурацией сервера.
Расширенные примеры и нестандартные сценарии блокировки домена
Пример 1: Проверка Origin с поддержкой нескольких протоколов и портов
function isOriginAllowed(string $origin, array $allowList): bool {
$parsed = parse_url($origin);
if (!$parsed || !isset($parsed['host'])) {
return false;
}
$host = $parsed['host'];
$scheme = $parsed['scheme'] ?? '';
$port = $parsed['port'] ?? '';
foreach ($allowList as $allowed) {
$aParsed = parse_url($allowed);
if ($aParsed && $aParsed['host'] === $host) {
// Проверка схемы и порта, если указаны
if ((!isset($aParsed['scheme']) || $aParsed['scheme'] === $scheme) &&
(!isset($aParsed['port']) || (int)$aParsed['port'] === (int)$port)) {
return true;
}
}
}
return false;
}
$allowed = ['https://mysite.com', 'http://localhost:8080'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (!isOriginAllowed($origin, $allowed)) {
http_response_code(403);
echo json_encode(['status' => 'denied']);
exit;
}
// Результат: при origin https://mysite.com - пропускает; при https://evil.com - 403
Пример 2: Комбинированная проверка Origin и Referer (двухфакторная)
$allowedDomain = 'mysite.com';
function checkDomain(string $url): bool {
$host = parse_url($url, PHP_URL_HOST) ?? '';
return preg_match('/^([a-z0-9-]+\.)*' . preg_quote($allowedDomain, '/') . '$/i', $host);
}
$valid = false;
if (!empty($_SERVER['HTTP_ORIGIN'])) {
$valid = checkDomain($_SERVER['HTTP_ORIGIN']);
}
if (!$valid && !empty($_SERVER['HTTP_REFERER'])) {
$valid = checkDomain($_SERVER['HTTP_REFERER']);
}
if (!$valid) {
http_response_code(403);
exit;
}
// Повышает надёжность: если Origin не передан, проверяется Referer.
Пример 3: Блокировка с логированием попыток и динамическим белым списком
$logFile = '/var/log/domain_block.log';
$allowedDomains = ['site1.com', 'site2.org'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? $_SERVER['HTTP_REFERER'] ?? '';
$host = parse_url($origin, PHP_URL_HOST);
if ($host && !in_array($host, $allowedDomains)) {
$log = sprintf("[%s] Blocked request from %s (IP: %s)\n", date('Y-m-d H:i:s'), $host, $_SERVER['REMOTE_ADDR']);
file_put_contents($logFile, $log, FILE_APPEND | LOCK_EX);
http_response_code(403);
exit;
}
// Логи сохраняются для анализа атак.
Пример 4: Использование заголовка X-Forwarded-Host за прокси
// Если приложение стоит за reverse proxy, реальный хост может быть в X-Forwarded-Host
$host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? $_SERVER['HTTP_HOST'] ?? '';
$allowed = ['app.mysite.com'];
if (!in_array($host, $allowed)) {
http_response_code(403);
exit;
}
// Важно: доверять X-Forwarded-Host только если прокси настроен на его установку.
Пример 5: Динамическая блокировка на основе регулярных выражений для поддоменов
$pattern = '/^https?:\/\/([a-z0-9-]+\.)*(mysite\.com|myother\.net)$/i';
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (!preg_match($pattern, $origin)) {
http_response_code(403);
exit;
}
// Пропускает любые поддомены mysite.com и myother.net.