Реализация проверки домена в 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.

Блокировка домена в PHP - comments

En
Domain block php (php)