↩️ Назад

Категории

PHP Security: от основ до продвинутого уровня

08.06.2026 | Статья из категории: php

PHP Security: Полное руководство по защите веб-приложений

Уровни: Начальный → Средний → Продвинутый (Enterprise-grade)
Актуальность: PHP 8.1+ (современные стандарты)


1. Базовый уровень защиты (Junior)

Это минимальный набор действий, который должен быть в каждом проекте. Игнорирование этих правил — 90% всех взломов.

1.1 SQL-инъекции: Только подготовленные запросы

Никогда не вставляйте переменные напрямую в SQL-строку. Даже если вы уверены в данных. Используйте PDO или MySQLi с подготовленными запросами.

<?php
// ❌ СМЕРТЕЛЬНО ОПАСНО:
$user = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $user";

// ✅ ПРАВИЛЬНО (PDO):
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);

// Для динамических LIKE-запросов - используйте явное экранирование через addcslashes
$search = addcslashes($_GET['q'], '%_');
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE ?");
$stmt->execute(["%$search%"]);
?>

1.2 XSS (межсайтовый скриптинг): Контекстное экранирование

Опасно не само значение, а контекст, куда оно попадает. Для HTML-контекста всегда используйте htmlspecialchars().

<?php
// ❌ ОПАСНО:
echo "<div>" . $_GET['comment'] . "</div>";

// ✅ БЕЗОПАСНО:
echo "<div>" . htmlspecialchars($_GET['comment'], ENT_QUOTES | ENT_HTML5, 'UTF-8') . "</div>";

// Для атрибутов HTML:
echo '<input value="' . htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8') . '">';
?>

1.3 Хранение паролей: password_hash() и Argon2id

Забудьте про md5(), sha1() и даже "соленый" md5. Используйте алгоритм, устойчивый к перебору.

<?php
// ✅ РЕГИСТРАЦИЯ:
$hash = password_hash($_POST['password'], PASSWORD_ARGON2ID); // Argon2id — лучший на 2026 год

// ✅ АУТЕНТИФИКАЦИЯ:
if (password_verify($_POST['password'], $hash_from_db)) {
    // Успех — при необходимости обновите хеш на более новый
    if (password_needs_rehash($hash_from_db, PASSWORD_ARGON2ID)) {
        $new_hash = password_hash($_POST['password'], PASSWORD_ARGON2ID);
        // сохраните $new_hash в БД
    }
}
?>

1.4 Защита от CSRF (межсайтовая подделка запроса)

Для форм, меняющих состояние (POST, PUT, DELETE), генерируйте одноразовые токены.

<?php
// Генерация токена:
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// В форме:
?>
<form method="POST">
    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
    <!-- остальные поля -->
</form>

<?php
// Проверка при отправке:
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
    die('CSRF token mismatch');
}
?>

2. Средний уровень защиты (Middle/Advanced Junior)

Эти методы требуют понимания инфраструктуры, но дают защиту от более сложных атак.

2.1 Безопасная работа с файлами и загрузками

Никогда не доверяйте $_FILES['file']['type'] — это подделывается браузером. Проверяйте реальное содержимое файла.

<?php
function isSafeImage($filePath) {
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $filePath);
    finfo_close($finfo);
    
    // Белый список MIME-типов
    $allowed = ['image/jpeg', 'image/png', 'image/webp'];
    return in_array($mime, $allowed, true);
}

// Дополнительная защита: переименование файлов и хранение вне document_root
$newName = bin2hex(random_bytes(16)) . '.' . pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$uploadPath = '/var/secure_uploads/' . $newName; // За пределами public_html
?>

2.2 Защита от инъекций в сериализацию

Не используйте unserialize() на пользовательских данных — это приводит к удаленному выполнению кода (RCE). Используйте json_decode() или sodium_crypto_secretbox() для подписанных данных.

<?php
// ❌ ОПАСНО (RCE через десериализацию):
$data = unserialize($_COOKIE['user_data']);

// ✅ БЕЗОПАСНО (JSON):
$data = json_decode($_COOKIE['user_data'] ?? '{}', true);

// Если нужна именно сериализация объектов — используйте подпись:
$signed = sodium_crypto_sign($serialized_data, $secret_key);
?>

2.3 HTTP-заголовки безопасности (настройка сервера через PHP)

Эти заголовки заставляют браузер включать встроенные механизмы защиты.

<?php
// Запрещаем браузеру MIME-сниффинг (предотвращает атаки типа "image upload to XSS")
header('X-Content-Type-Options: nosniff');

// Включаем CSP (Content Security Policy) — разрешаем только свои скрипты
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'? 'unsafe-eval'?; style-src 'self'");

// HTTP Strict Transport Security (только если у вас HTTPS)
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');

// Защита от clickjacking
header('X-Frame-Options: DENY');

// Referrer Policy — не передаём referer при переходе на другой домен
header('Referrer-Policy: same-origin');
?>

2.4 Безопасные куки

<?php
session_set_cookie_params([
    'lifetime' => 3600,
    'path' => '/',
    'domain' => $_SERVER['HTTP_HOST'],
    'secure' => true,      // только по HTTPS
    'httponly' => true,    // недоступны для JavaScript (защита от XSS)
    'samesite' => 'Strict' // защита от CSRF
]);
session_start();
?>

3. Продвинутый уровень защиты (Senior/Architect/Enterprise)

Требует глубокого понимания криптографии, инфраструктуры и PHP internals. Эти методы характерны для высоконагруженных проектов, финтеха, медицинских систем.

3.1 Шифрование данных в покое (Data-at-Rest Encryption)

HTTPS защищает данные при передаче, но не в базе данных. Если злоумышленник получит дамп БД — данные должны быть нечитаемы. Используйте libsodium (встроен в PHP).

<?php
// Генерация мастер-ключа (храните вне БД, например в .env или AWS KMS)
$secretKey = sodium_crypto_aead_xchacha20poly1305_ietf_keygen();

// Шифрование
function encryptSensitive($plaintext, $key) {
    $nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
    $ciphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
        $plaintext, 
        '',     // дополнительные данные (можно указать ID пользователя)
        $nonce, 
        $key
    );
    return base64_encode($nonce . $ciphertext);
}

// Дешифрование
function decryptSensitive($encrypted, $key) {
    $decoded = base64_decode($encrypted);
    $nonce = substr($decoded, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
    $ciphertext = substr($decoded, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
    return sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, '', $nonce, $key);
}
?>

3.2 Rate Limiting и защита от брутфорса

На уровне PHP + Redis или Memcached. Используйте алгоритм "скользящего окна" или token bucket.

<?php
// Пример с Redis (токен-бакет)
$redis = new Redis();
$key = 'rate_limit:' . $_SERVER['REMOTE_ADDR'] . ':login';
$maxRequests = 10;     // 10 попыток
$window = 60;          // за 60 секунд

$current = $redis->get($key);
if ($current === false) {
    $redis->setex($key, $window, 1);
    $allow = true;
} elseif ($current < $maxRequests) {
    $redis->incr($key);
    $allow = true;
} else {
    $allow = false;
    http_response_code(429); // Too Many Requests
    die('Превышен лимит попыток. Попробуйте через ' . $redis->ttl($key) . ' секунд');
}
?>

3.3 Защита от атак по времени (Timing attacks) и Side-channel атак

Стандартные операторы сравнения (==, ===) могут утечь информацию через время выполнения. Для секретных данных (токены, хеши) используйте hash_equals() и sodium_memcmp().

<?php
// ❌ Уязвимо к timing attack:
if ($submitted_token === $real_token) { ... }

// ✅ Константное время сравнения:
if (hash_equals($real_token, $submitted_token)) { ... }

// Для очистки чувствительных данных из памяти (после использования):
sodium_memzero($password_variable);
?>

3.4 Использование Web Application Firewall (WAF) на уровне PHP (ModSecurity / Coraza)

Для критических проектов — запускайте PHP под управлением WAF. Пример с Coraza (Go-совместимый ModSecurity) или PHP-fpm + nginx + ModSecurity. На чистом PHP можно написать "примитивный WAF":

<?php
class WAF {
    private static $patterns = [
        '/select.*from/i' => 'SQLi attempt',
        '/union.*select/i' => 'SQLi attempt',
        '/\b(wget|curl|nc|bash|sh)\b/i' => 'Command injection',
        '/\.\.\/|\.\.\\\/' => 'Path traversal',
        '/<\?php/i' => 'Code injection',
    ];
    
    public static function scan($input) {
        foreach (self::$patterns as $pattern => $reason) {
            if (preg_match($pattern, $input)) {
                error_log("WAF blocked: $reason - Input: $input");
                http_response_code(403);
                die('Request blocked by security policy');
            }
        }
    }
}

// Применять ко всем входным данным:
WAF::scan($_SERVER['REQUEST_URI']);
array_walk_recursive($_GET, 'WAF::scan');
array_walk_recursive($_POST, 'WAF::scan');
?>

3.5 Защита от XXE (XML eXternal Entity) и десериализация XML

<?php
// При работе с XML (например, SOAP, XML-RPC) — обязательно отключайте внешние сущности
$dom = new DOMDocument();
$dom->loadXML($xmlInput, LIBXML_NOENT | LIBXML_DTDLOAD);

// Или через SimpleXML:
$sxe = simplexml_load_string($xmlInput, 'SimpleXMLElement', LIBXML_NOENT);
// Для старых версий: libxml_disable_entity_loader(true);
?>

3.6 Использование безопасных случайных чисел: random_int() вместо rand() или mt_rand()

rand() и mt_rand() предсказуемы для криптографических целей. Всегда используйте CSPRNG.

<?php
// ❌ Предсказуемо:
$token = md5(rand(10000, 99999));

// ✅ Криптографически безопасно:
$token = bin2hex(random_bytes(32));

// Если random_bytes недоступен (старый PHP) — проверяем:
try {
    $bytes = random_bytes(32);
} catch (Exception $e) {
    // fallback: использовать /dev/urandom или openssl
    $bytes = openssl_random_pseudo_bytes(32);
}
?>

4. Чек-лист для аудита безопасности PHP-проекта

Распечатайте и проверяйте каждый пункт при деплое.

Обязательные (критические):

  • Все SQL-запросы используют подготовленные выражения (PDO / MySQLi)
  • Любой вывод пользовательских данных проходит через htmlspecialchars()
  • Пароли хешируются через password_hash() с PASSWORD_ARGON2ID
  • Включена защита от CSRF для всех изменяющих запросов
  • display_errors = Off в production, ошибки пишутся в лог
  • Запрещены функции eval(), exec(), system() с пользовательским вводом
  • Файлы загрузок хранятся вне document_root

Рекомендуемые (повышенная безопасность):

  • Настроен CSP (Content-Security-Policy) заголовок
  • Куки имеют флаги HttpOnly, Secure, SameSite=Strict
  • Включено логирование всех неудачных аутентификаций
  • Чувствительные данные в БД зашифрованы (sodium)
  • Настроен rate limiting на критичных эндпоинтах
  • Используется random_bytes() для генерации токенов
  • Все сравнения секретных данных через hash_equals()

Enterprise-level:

  • Используется WAF (ModSecurity или аналог)
  • Внедрена двухфакторная аутентификация (TOTP / WebAuthn)
  • Есть система обнаружения вторжений (IDS) на уровне PHP
  • Все зависимости (Composer) проверяются на уязвимости (composer audit)
  • Проводится статический анализ кода (Psalm, PHPStan с security-правилами)

Полезные инструменты и ресурсы

  • Composer security audit: composer require --dev roave/security-advisories
  • Static analysis: Psalm with security level или PHPStan с расширением phpstan/phpstan-strict-rules
  • Пентест-фреймворк: RIPS (статический анализатор безопасности PHP)
  • Документация: OWASP PHP Security Cheat Sheet — актуальный стандарт
  • Для тестов: Docker-образ с уязвимым PHP приложением (DVWA, bWAPP) для практики защиты

Статья актуальна на 2026 год. Помните: безопасность — это процесс, а не одноразовое действие. Регулярно обновляйте PHP и библиотеки.




Категории:

Категории

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA