Сессии в PHP: как работают и как защитить от перехвата

Сессии в PHP: как работают и как защитить от перехвата Полезное

Сессия в PHP — это способ хранить данные пользователя на сервере между несколькими HTTP-запросами, привязывая их к конкретному браузеру через уникальный идентификатор. В статье разберем, как PHP создает и завершает сессию, чем она отличается от cookie и какие настройки нужны, чтобы сессию нельзя было перехватить или подделать.

Cookie и сессия: в чем разница

Эти два понятия путают чаще всего, хотя они решают разные задачи.

Cookie — это пара ключ-значение, которую браузер хранит у себя и отправляет на сервер с каждым запросом к тому же домену. Сами данные лежат на стороне клиента.

Сессия — это данные, которые хранятся на сервере (по умолчанию — в файле). Браузеру передается только идентификатор сессии, а не сами данные.

Связывает их одна cookie с именем PHPSESSID (имя можно изменить директивой session.name). В ней лежит только идентификатор — ключ, по которому сервер находит нужный файл сессии. Если эту cookie удалить или подделать, PHP не найдет сохраненные данные и начнет новую сессию.

Как PHP создает и ведет сессию

При вызове session_start() происходит несколько шагов:

  1. PHP проверяет, пришла ли от клиента cookie PHPSESSID. Если да — ищет файл сессии с таким идентификатором.
  2. Если cookie нет (первый визит), PHP генерирует новый идентификатор — строку по умолчанию длиной 32 символа, случайную и криптографически стойкую (с PHP 7.1 — на основе random_bytes, не предсказуемый счетчик).
  3. Сервер отправляет браузеру cookie PHPSESSID с этим идентификатором.
  4. На сервере создается файл sess_<идентификатор> в каталоге, который задан параметром session.save_path (php.ini). Префикс файла — ровно sess_, без дополнительных букв.
  5. Данные, которые скрипт кладет в массив $_SESSION, PHP сериализует и записывает в этот файл при завершении запроса.

На каждой следующей странице, где вызван session_start() с той же cookie, PHP читает файл и восстанавливает $_SESSION — так сервер «узнает» пользователя между запросами, хотя сам HTTP-протокол состояния не хранит.

Базовые операции: код с проговоренным результатом

Запуск сессии и запись значений:

<?php
session_start();

$_SESSION['user_id'] = 42;
$_SESSION['username'] = 'php_user';

echo 'Сессия запущена, id: ' . session_id();

Результат в браузере — строка вида Сессия запущена, id: 8f14e45fceea167a5a36dedd4bea2543 (конкретные символы каждый раз случайны, важна их длина и то, что это одна и та же строка при повторных запросах из того же браузера).

Чтение значений на другой странице того же сайта (нужен session_start() в начале файла):

<?php
session_start();

if (isset($_SESSION['username'])) {
    echo 'Привет, ' . htmlspecialchars($_SESSION['username']);
} else {
    echo 'Сессия не найдена, нужна авторизация';
}

Если cookie PHPSESSID от предыдущего запроса сохранилась, скрипт выведет Привет, php_user. Без cookie (например, в приватном окне) — выведет вторую строку, потому что стартует новая пустая сессия.

Полное завершение сессии — частая ошибка в том, что session_destroy() удаляет файл на сервере, но не трогает cookie в браузере. Правильный вариант закрытия сессии выглядит так:

<?php
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();

Здесь три шага: очистить массив в памяти, явно затереть cookie на клиенте с истекшим сроком, и только затем удалить файл сессии на сервере. Без второго шага браузер продолжит отправлять старый PHPSESSID, даже если файла с таким именем на сервере уже нет.

Функция Что делает
session_start() открывает сессию: читает существующую или создает новую
$_SESSION суперглобальный массив для чтения и записи данных сессии
session_unset() очищает массив $_SESSION в текущем запросе
session_destroy() удаляет файл сессии на сервере
session_regenerate_id() меняет идентификатор сессии, сохраняя данные
session_id() / session_name() возвращают текущий id сессии / имя ее cookie

Защита сессии: фиксация, перехват, CSRF

Сессия хранит данные о вошедшем пользователе, поэтому ее защита — не опциональная надстройка, а обязательная часть логина. У трех типовых угроз разная природа и разная защита, путать их нельзя.

Session fixation. Атакующий заранее навязывает жертве известный ему идентификатор сессии (например, через ссылку с параметром), а после того как жертва вводит в эту сессию логин и пароль, использует тот же идентификатор для доступа. Защита — обязательно вызывать session_regenerate_id(true) сразу после успешной проверки логина и пароля, до того как в $_SESSION попадут данные авторизованного пользователя:

<?php
session_start();

// Логин и пароль уже проверены выше по коду

session_regenerate_id(true); // true - удалить старый файл сессии на сервере
$_SESSION['user_id'] = $user['id'];
$_SESSION['authenticated'] = true;

Параметр true важен: без него старый файл сессии останется на диске, и если атакующий успел его подсмотреть до регенерации, он какое-то время еще будет действовать.

Session hijacking (кража идентификатора). Если id сессии перехвачен — через XSS на странице или прослушивание незащищенного соединения, — атакующий может выдать себя за пользователя без пароля. От чтения cookie через JavaScript защищает флаг httponly, от перехвата в сети — флаг secure (cookie отправляется только по HTTPS). Задать их лучше до вызова session_start():

<?php
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => '',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

session_start();

Флаг secure требует, чтобы сайт действительно работал по HTTPS — на локальном сервере без TLS браузер такую cookie не примет вовсе, и сессия перестанет сохраняться. Это частая причина, по которой «на проде работает, локально нет».

CSRF (подделка межсайтового запроса). Здесь cookie сессии не крадут, а используют штатно: браузер сам подставляет ее в запрос, отправленный со стороннего сайта. Атрибут samesite (Lax или Strict) ограничивает, в каких межсайтовых запросах браузер вообще подставляет cookie, но для форм и API это стоит дополнять отдельным CSRF-токеном — samesite снижает риск, но не заменяет токен полностью, особенно для GET-переходов при Lax.

Угроза В чем суть Основная защита
Session fixation Атакующий подсовывает свой id сессии до входа жертвы session_regenerate_id(true) после логина
Session hijacking id сессии украден (XSS, сеть) httponly, secure, HTTPS
CSRF Запрос со стороннего сайта использует чужие cookie samesite + CSRF-токен в форме

Отдельно стоит хранение самого файла сессии. На виртуальном хостинге с общим session.save_path файлы сессий разных сайтов могут физически лежать в одном каталоге — если права на каталог настроены слабо, это риск чтения чужих сессий. Для проектов на нескольких серверах файловое хранилище вообще не подходит — обычно используют Redis или базу данных через свой обработчик сессий (session_set_save_handler), а не одну общую файловую систему.

Актуальная версия PHP и настройки сессии

Механизм сессий, описанный выше, стабилен с PHP 7.1 (криптостойкая генерация id) и работает без изменений в текущих активных ветках PHP 8.x. На момент публикации актуальны ветки PHP 8.3-8.5, где ежегодный релиз-цикл PHP не менял ни session_start(), ни формат $_SESSION. Точный номер последней минорной версии и статус ее поддержки на дату чтения статьи есть на официальной странице поддерживаемых версий php.net.

Выводы

  • Сессия хранит данные на сервере, cookie на клиенте передает только ее идентификатор — это разные механизмы.
  • Жизненный цикл: session_start() создает или продолжает сессию, $_SESSION хранит данные, session_destroy() вместе с затиранием cookie завершает ее полностью.
  • После успешного логина обязательна session_regenerate_id(true) — это защита от session fixation.
  • Флаги httponly, secure и samesite закрывают разные угрозы (кража id, перехват в сети, CSRF) и нужны все три вместе, а не по отдельности.
  • На многосерверных проектах файловое хранилище сессий заменяют на Redis или БД через собственный обработчик.

Где применяется / связь с практикой

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

Работа с сессиями — база любой авторизации на PHP: личный кабинет, корзина в интернет-магазине, админ-панель. Если хотите разобрать эту тему системно вместе с остальным стеком backend-разработки на PHP, посмотрите курс «Разработчик PHP» — там разбирают безопасность веб-приложений на реальных проектах. Проверить формат подачи можно на открытых уроках Otus — они бесплатные и идут регулярно по разным направлениям.

FAQ

Можно ли хранить в сессии PHP объекты, а не только строки и числа?
Да, PHP сериализует любые типы, включая массивы и объекты, при условии что класс объекта доступен на момент десериализации (обычно достаточно автозагрузки классов).

Что будет, если вызвать session_start() дважды за один запрос?
PHP выдаст notice о повторном старте сессии и проигнорирует второй вызов, действующей останется первая инициализация.

Сколько живет сессия, если пользователь ничего не делает на сайте?
По умолчанию файл сессии считается устаревшим через session.gc_maxlifetime секунд (обычно 1440, то есть 24 минуты) простоя, но фактическое удаление зависит от вероятностного сборщика мусора session.gc_probability/session.gc_divisor, поэтому точное время нужно проверять по настройкам конкретного сервера.

OTUS Журнал