Идентификатор сеанса является ключом к учетной записи вашего пользователя. Здесь описаны все способы, которыми злоумышленники его крадут и как их остановить.
Это двенадцатая статья в серии о безопасности приложений PHP и Laravel.
На данный момент мы рассмотрели:
- Обнаружение попыток внедрения SQL в журналах PHP
- Почему кодирование URL-адресов скрывает большинство проверок безопасности PHP
- Проблема декодирования бомбы с неограниченным декодированием URL-адресов
- Почему параметризованные запросы являются единственным реальным решением проблемы SQL-инъекций
- Предотвращение XSS в Laravel и почему {!! !!} — это грань между безопасным и взломанным
- Как злоумышленники проверяют ваше приложение Laravel, прежде чем использовать его
- Безопасность загрузки файлов — файл не является тем, чем он заявлен
- Обход пути в PHP — как ../ обходит ваше приложение
- Внедрение команд в PHP — когда exec() становится поверхностью атаки
- Сломанный контроль доступа в Laravel — почему недостаточно войти в систему
- Секреты Laravel — почему .env — это только начало
Все статьи этой серии следуют одному и тому же принципу: поймите атаку, прежде чем пытаться ее остановить.
Безопасность сеанса отличается от большинства уязвимостей в этой серии. SQL-инъекция, XSS и обход пути напрямую атакуют ваше приложение. Сеансовые атаки атакуют уровень идентификации — механизм, который доказывает, кем является пользователь после его аутентификации. Если вы сделаете ошибку, не будет иметь значения, насколько безопасна остальная часть вашего приложения. Злоумышленник с действительным идентификатором сеанса неотличим от законного пользователя.
Как работают сеансы PHP
Когда пользователь впервые посещает ваше PHP-приложение, PHP создает сеанс:
- PHP генерирует случайный идентификатор сеанса — длинную криптографически случайную строку.
- PHP сохраняет этот идентификатор сеанса в файле cookie в браузере пользователя, который по умолчанию называется PHPSESSID .
- PHP хранит данные сеанса на сервере с ключом по этому идентификатору сеанса.
- При каждом последующем запросе браузер автоматически отправляет файл cookie сеанса.
- PHP считывает идентификатор сеанса из файла cookie, ищет данные на стороне сервера и восстанавливает сеанс.
Идентификатор сеанса является ключом ко всему. Тот, кто владеет этим идентификатором сеанса, может выдавать себя за пользователя, которому он принадлежит. Это то, на что нацелена каждая сессионная атака.
Атака 1 — перехват сеанса
Перехват сеанса — это когда злоумышленник крадет действительный идентификатор сеанса и использует его, чтобы выдать себя за законного пользователя.
Способ 1 — XSS:
Если в вашем приложении есть XSS-уязвимость, злоумышленник внедряет JavaScript, который считывает файл cookie сеанса и отправляет его на свой сервер. Вот почему существует атрибут cookie HttpOnly : он полностью запрещает JavaScript читать файлы cookie.
Способ 2. Сетевой перехват:
Если ваше приложение работает через HTTP без SSL, злоумышленник в той же сети может перехватить файл cookie сеанса из незашифрованного трафика. Вот почему существует атрибут cookie Secure , который сообщает браузеру отправлять файлы cookie только через HTTPS.
Способ 3. Идентификаторы сеансов в URL-адресах:
Некоторые PHP-приложения передают идентификаторы сеансов в URL-адресах:
https://yoursite.com/dashboard?PHPSESSID=abc123def456
Идентификаторы сеансов на основе URL-адресов отображаются в журналах сервера, истории браузера и заголовках источников перехода, когда пользователи нажимают внешние ссылки. Для их кражи не требуется никакой технической атаки, достаточно доступа к файлу журнала.
Способ 4. Предсказуемые идентификаторы сеансов:
Если идентификаторы сеансов генерируются со слабой случайностью, злоумышленник может их предсказать или перебрать. Генерация идентификатора сеанса PHP по умолчанию является криптографически безопасной, но генерация пользовательского идентификатора сеанса в старых приложениях часто не является таковой.
Атака 2 — Фиксация сеанса
Фиксация сессии более тонкая, чем перехват. Злоумышленник не крадет идентификатор сеанса, он заставляет жертву использовать идентификатор сеанса, который уже известен злоумышленнику.
Последовательность атаки:
- Злоумышленник посещает ваше приложение и получает действительный идентификатор сеанса: abc123 .
- Злоумышленник обманом заставляет жертву использовать тот же идентификатор сеанса, отправляя ей созданный URL-адрес: https://yoursite.com/login?PHPSESSID=abc123
- Жертва входит в систему, используя идентификатор сеанса abc123
- Если ваше приложение не генерирует новый идентификатор сеанса после входа в систему, сеанс abc123 теперь связан с аутентифицированной учетной записью жертвы.
- Злоумышленник использует идентификатор сеанса abc123 , который он знал с самого начала, для доступа к учетной записи жертвы.
Жертва успешно залогинилась. Злоумышленник никогда не знал своего пароля. Атака сработала, поскольку идентификатор сеанса не изменился после аутентификации.
Исправление — это вызов одной функции, но это один из наиболее часто пропускаемых элементов управления безопасностью в приложениях PHP.
Конфигурация сеанса PHP. Базовый план производства
Значения PHP по умолчанию обеспечивают базовый уровень, но производственные приложения должны явно проверять и ужесточать конфигурацию сеанса, а не слепо полагаться на значения по умолчанию. Эти настройки следует проверять для каждого производственного приложения PHP:
; php.ini ; Never accept session IDs in URLs - only in cookies session.use_only_cookies = 1 ; Prevent JavaScript from reading session cookies session.cookie_httponly = 1 ; Only send session cookies over HTTPS session.cookie_secure = 1 ; Restrict cookie to same-site requests session.cookie_samesite = Lax ; Reject unrecognized session IDs session.use_strict_mode = 1 ; Set a reasonable session lifetime session.gc_maxlifetime = 1440
Самый важный параметр:
session.use_only_cookies = 1
Это не позволяет PHP принимать идентификаторы сеансов, передаваемые в URL-адресах. В сочетании с session.use_strict_mode = 1 , который отклоняет идентификаторы сеансов, которые PHP не создал, эти два параметра устраняют наиболее распространенный вектор атаки с фиксацией сеанса на уровне конфигурации.
Исправление фиксации сеанса — восстановление сеанса
Одной конфигурации недостаточно. Ваше приложение должно повторно генерировать идентификатор сеанса после каждого успешного входа в систему:
session_start();
if ($credentialsAreValid) {
// Regenerate the session ID and delete the old session data
session_regenerate_id(true);
// Now set authenticated session data
$_SESSION['user_id'] = $user->id;
$_SESSION['authenticated'] = true;
}Параметр true в session_regenerate_id(true) удаляет старые данные сеанса на сервере. Рекомендация по безопасности состоит в том, чтобы повторно создать идентификатор сеанса после аутентификации и аннулировать старое состояние сеанса, где это возможно. Это удаляет окно, которое злоумышленник мог бы использовать, если бы у него был предыдущий идентификатор сеанса.
Также выполнять регенерацию при изменении привилегий:
// Any time a user's privileges change — not just on login session_regenerate_id(true); $_SESSION['role'] = 'admin';
Атрибуты безопасности файлов cookie
Для каждого файла cookie сеанса требуются три атрибута безопасности:
HttpOnly — запрещает JavaScript читать файлы cookie:
session_set_cookie_params(['httponly' => true]);
Без этого любая XSS-уязвимость в вашем приложении может украсть файлы cookie сеанса.
Безопасно — только HTTPS:
session_set_cookie_params(['secure' => true]);
Без этого файл cookie сеанса передается по незашифрованным HTTP-соединениям, где его можно перехватить.
SameSite — контролирует отправку файлов cookie между сайтами:
session_set_cookie_params(['samesite' => 'Lax']);
Lax отправляет файл cookie с навигацией верхнего уровня, но не со встроенными межсайтовыми запросами. Это обеспечивает значимую защиту от CSRF, сохраняя при этом большинство законных вариантов использования.
Strict никогда не отправляет файлы cookie с межсайтовыми запросами — самая надежная защита, но нарушает такие процессы, как нажатие внешней ссылки, которая должна привести пользователя в состояние аутентификации.
Нет всегда отправляет файл cookie, который используется только для случаев намеренного межсайтового использования и только с атрибутом Secure .
Полная настройка безопасного сеанса на простом PHP:
// Call before session_start()
session_set_cookie_params([
'lifetime' => 0, // Expires when browser closes
'path' => '/',
'domain' => '',
'secure' => true, // HTTPS only
'httponly' => true, // No JavaScript access
'samesite' => 'Lax', // CSRF protection
]);
session_start();Безопасность хранилища сеансов
По умолчанию PHP хранит данные сеанса в файлах /tmp . В средах общего хостинга другие арендаторы могут иметь возможность читать ваши файлы сеанса в зависимости от конфигурации сервера.
Хранилище базы данных:
Хранение сеансов в базе данных дает вам лучший контроль доступа и возможность аннулировать определенные сеансы, что полезно, когда вам нужно принудительно выйти из системы скомпрометированного пользователя:
session_set_save_handler(new DatabaseSessionHandler($pdo), true); session_start();
Redis:
Redis — хороший выбор для приложений, работающих на нескольких серверах или требующих централизованного хранилища сеансов. Сеансы базы данных — еще один практичный вариант производства, зависящий от архитектуры вашего хостинга и требований к изоляции:
; php.ini session.save_handler = redis session.save_path = "tcp://127.0.0.1:6379"
Конфигурация сеанса Laravel
Laravel абстрагирует сеансы PHP за своей собственной системой сеансов. Конфигурация находится в config/session.php .
Критические производственные настройки:
// config/session.php
// Encrypt session data at rest
'encrypt' => true,
// HTTPS only session cookie
'secure' => env('SESSION_SECURE_COOKIE', true),
// Prevent JavaScript from reading the cookie
'http_only' => true,
// CSRF protection
'same_site' => 'lax',
// Session lifetime in minutes
'lifetime' => env('SESSION_LIFETIME', 120),
// Session storage driver
'driver' => env('SESSION_DRIVER', 'redis'),Шифрование сеанса:
Параметр 'encrypt' => true шифрует все данные сеанса с помощью Laravel APP_KEY . Даже если злоумышленник получит доступ к хранилищу ваших сеансов, он не сможет прочитать содержимое сеанса без ключа шифрования.
Драйвер сеанса:
Никогда не используйте драйвер файла в рабочей среде на общем хостинге. Используйте базу данных или redis для правильного управления доступом и автоматического завершения сеанса.
Регенерация сеанса в Laravel
Laravel автоматически восстанавливает идентификатор сеанса при входе в систему, когда вы используете Auth::attempt() или Auth::login() . Это встроено в структуру.
Но если вы создаете пользовательскую аутентификацию или меняете привилегии вручную, вам необходимо явно перегенерировать:
// Regenerate session ID — keep existing data $request->session()->regenerate(); // Regenerate and invalidate the old session completely $request->session()->invalidate(); $request->session()->regenerateToken();
regenerateToken() восстанавливает токен CSRF, хранящийся в сеансе. Вызовите это вместе с регенерацией сеанса после входа в систему. Фиксация токена CSRF отражает фиксацию сеанса и должна выполняться одновременно.
Абсолютный тайм-аут сеанса
Одного таймаута простоя недостаточно. Пользователь, который остается активным, теоретически может поддерживать сеанс бесконечно. Абсолютный тайм-аут приводит к повторной аутентификации через фиксированный период времени независимо от активности:
// Store login time in session
$_SESSION['login_time'] = time();
// On every authenticated request
$absoluteTimeout = 8 * 60 * 60; // 8 hours
if (time() - $_SESSION['login_time'] > $absoluteTimeout) {
session_destroy();
header('Location: /login?reason=timeout');
exit;
}В Ларавеле:
// config/session.php 'lifetime' => 480, // 8 hours in minutes
Для приложений, обрабатывающих конфиденциальные данные, рассмотрите более короткие абсолютные таймауты. Приложения, обрабатывающие финансовые данные или личную информацию клиента, должны чаще требовать повторной аутентификации.
Безопасный выход
Всегда полностью уничтожайте сеанс при выходе из системы, а не просто сбрасывайте переменные сеанса:
// Plain PHP — wrong approach
unset($_SESSION['user_id']); // Session ID still valid
// Plain PHP — correct approach
session_start();
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
session_destroy();В Laravel правильная последовательность выхода из системы состоит из трех шагов:
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');Каждый шаг имеет значение:
- Auth::logout() очищает состояние аутентификации и удаляет пользователя из сеанса
- session()->invalidate() уничтожает сеанс и генерирует новый идентификатор сеанса.
- session()->regenerateToken() восстанавливает токен CSRF.
Отсутствие любого из этих шагов делает старый сеанс потенциально уязвимым.
Контрольный список безопасности сеанса
Для простого PHP:
- session.use_only_cookies = 1 — никогда не принимать идентификаторы сеансов в URL-адресах.
- session.use_strict_mode = 1 — отклонять нераспознанные идентификаторы сеансов.
- session.cookie_httponly = 1 — запретить JavaScript читать файлы cookie.
- session.cookie_secure = 1 — только HTTPS
- session.cookie_samesite = Lax — защита CSRF
- Call session_regenerate_id(true) after every successful login
- Call session_regenerate_id(true) after every privilege change
- Полностью уничтожить сеанс при выходе из системы, а не просто удалить переменные.
- Используйте базу данных или хранилище сеансов Redis в рабочей среде.
- Реализуйте абсолютный тайм-аут сеанса для конфиденциальных приложений.
Для Ларавеля:
- 'secure' => true в config/session.php
- 'http_only' => true в config/session.php
- 'encrypt' => true в config/session.php
- 'same_site' => 'lax' в config/session.php
- Используйте драйвер сеанса базы данных или redis в рабочей среде.
- Вызов всех трех методов выхода из системы при каждом выходе из системы.
- Никогда не создавайте пользовательскую аутентификацию, которая пропускает регенерацию сеанса после входа в систему.
Комментарии (0)
Пока нет комментариев — будьте первым.