Категории
PHP Security: от основ до продвинутого уровня
08.06.2026 | из категории: phpPHP 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-правилами)