PHP сессии под скальпелем. Как работают PHP сессии изнутри

        
         1 комментарий

В Интернете можно найти тысячи туториалов о том, что такое сессии, для чего они нужны и как с ними работать. Но, к сожалению, прочитав их, всё равно остаётся множество вопросов. На мой взгляд, самый простой способ разобраться во всём — это посмотреть, как работают сессии изнутри. Т.е. изучить логи обмена браузера и веб-сервера, а также посмотреть, какие данные сохраняются на стороне клиента и на стороне сервера.

После этого многие моменты становятся куда более понятными, а сам механизм работы — более прозрачным.

Работу сессий будем изучать на следующем стандартном скрипте:

<?php
 
// блок 1
session_start(); 
 
// блок 2
if(isset($_SESSION['views']))
    $_SESSION['views']++;
else
    $_SESSION['views'] = 1;
 
// блок 3
echo "<html>
<body>
Количество просмотров: ".$_SESSION['views']."
</body>
</html>";
 
?>

Он работает следующим образом:

Блок 1. Функция session_start() создаёт новую сессию или загружает старую, используя уникальный идентификатор сессии PHPSESSID.

Блок 2. Если удалось восстановить сессию, то значение $_SESSION['views'] увеличивается на единицу. Если нет — инициализируется единицей.

Блок 3. Выводится количество просмотров данной страницы.

По идее, если в браузере включена поддержка кук, механизм должен работать и при каждом обновлении страницы значение счётчика будет увеличиваться на единицу.

Первая загрузка скрипта

Заголовки запроса

GET / HTTP/1.1
Host: firingrange.local
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Cache-Control: max-age=0

Заголовки ответа

HTTP/1.1 200 OK
Date: Thu, 29 Sep 2011 20:36:15 GMT
Server: Apache/2.2.13 (Win32) PHP/5.2.10
X-Powered-By: PHP/5.2.10
Set-Cookie: PHPSESSID=k33en6ccgcia7125mitj5te4u6; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 58
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Комментарий

В исходном запросе браузер не идентифицировал себя никаким образом, поэтому механизм сессий PHP сгенерировал новый уникальный идентификатор сессии и скомандовал браузеру создать куку, в которой будет храниться этот самый идентификатор.

Сторона сервера

В результате работы скрипта на стороне сервера создаётся файл sess_k33en6ccgcia7125mitj5te4u6 следующего содержания:

views|i:1;

Сторона клиента

На стороне клиента создаётся кука PHPSESSID, в которой хранится значение уникального идентификатора сессии.

Примечание. При настройках PHP по умолчанию, время жизни куки PHPSESSID — до закрытия браузера. Т.е. как только браузер будет закрыт, кука будет удалена, а соответственно будет потеряна сессия. Время жизни куки PHPSESSID можно менять, варьируя значение session.cookie_lifetime.

Результат работы скрипта

Вторая загрузка скрипта

Заголовки запроса

GET / HTTP/1.1
Host: firingrange.local
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Cookie: PHPSESSID=k33en6ccgcia7125mitj5te4u6
Cache-Control: max-age=0

Заголовки ответа

HTTP/1.1 200 OK
Date: Thu, 29 Sep 2011 20:49:41 GMT
Server: Apache/2.2.13 (Win32) PHP/5.2.10
X-Powered-By: PHP/5.2.10
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 58
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Комментарий

Браузер отправляет веб-серверу куку PHPSESSID, используя которую PHP инициализирует массив $_SESSION значениями из файла sess_k33en6ccgcia7125mitj5te4u6. Соответственно, в блоке 2 отрабатывает ветка IF (прямая).

Сторона сервера

В результате работы скрипта содержимое файла sess_k33en6ccgcia7125mitj5te4u6 меняется:

views|i:2;

Сторона клиента

На стороне клиента ничего не меняется.

Результат работы скрипта

Что дальше?

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

Т.к. время жизни куки было ограничено текущей сессией браузера, то после его закрытия уникальный идентификатор сессии будет утерян и при перезапуске процесс пойдёт по новой.

Тем не менее можно вернуться к сохранённой сессии, если явно указать PHPSESSID в качестве параметра скрипта:

Возвращение к сессии довольно условное, т.к. в результате работы скрипта в данном случае кука не создаётся. Заголовки ответа сервера:

HTTP/1.1 200 OK
Date: Thu, 29 Sep 2011 21:01:52 GMT
Server: Apache/2.2.13 (Win32) PHP/5.2.10
X-Powered-By: PHP/5.2.10
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 58
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html


Т.е. для поддержания работы именно с этой сессией ко всем ссылкам придётся приписывать ?PHPSESSID=k33en6ccgcia7125mitj5te4u6.

Примечание. Можно указать PHP, чтобы уникальный идентификатор сессии передавался только через куку. Для этого нужно установить session.use_only_cookies в значение 1. В этом случае трюк, продемонстрированный выше, не пройдёт.

Если куки в браузере отключены, то можно передавать идентификатор сессии через параметры, как мы делали выше. Причём в PHP есть механизм, который будет сам дописывать нужный параметр в ссылки и добавлять скрытые поля в формы. Принцип работы точно такой же, как и с куками, поэтому не будем разбирать этот случай отдельно.

Небольшой вопросник (FAQ)

Где физически хранятся данные сессий?

Данные сессий хранятся на сервере. По умолчанию они записываются в файлы, но можно задать свой собственный механизм хранения данных сессий (например с использованием базы данных). Если хотите подробностей, смотрите функцию session_set_save_handler.

Кто генерирует уникальный идентификатор сессии?

Уникальный идентификатор сессии (PHPSESSID) генерирует сервер.

Можно ли написать собственный механизм сессий?

Да, это вполне возможно. Как видите, PHP не использует ничего сверхъестественного — идентификатор сохраняется между запросами с помощью кук,  данные сессий хранятся в файлах на сервере.

Например, собственный механизм работы с сессиями есть в популярном фреймворке CodeIgniter.

Насколько безопасен механизм сессий?

Сессия идентифицируется только с помощью уникального идентификатора сессии, поэтому в общем случае злоумышленнику достаточно украсть его, чтобы запутать сервер. Возьмём тестовый скрипт, который мы использовали выше. Если обращение к нему будет с другого IP (по отношению к создавшему сессию), но PHPSESSID будет передаваться тот же самый, то сессия будет успешно восстановлена и счётчик будет увеличиваться с предыдущего сохранённого значения.

Обеспечивать дополнительную защиту придётся вам самим. Например:

  • Можно сохранять в данных сессии IP и User-Agent клиента (будет храниться на стороне сервера), а затем при каждом обращении проверять, что актуальные значения совпадают с сохранёнными. В данном случае приходится искать компромисс между безопасностью и удобством работы пользователя.

    К примеру, если у пользователя динамический IP и вы используете сессии для поддержания авторизации, но при этом проверяете совпадение IP, то при каждой смене адреса пользователю придётся заново вводить логин и пароль.

    Точно также строка User-Agent может меняться при обновлении версии браузера или при установке некоторых плагинов.
  • Одним из рекомендуемых механизмов защиты сессий является повторная генерация идентификатора при каждом обращении к скрипту (см. функцию session_regenerate_id). Посмотреть скрипт и алгоритм работы в разрезе можно ниже.

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

Работа сессий с повторной генерацией идентификатора в разрезе

Скрипт

<?php
 
// блок 1
session_start();
if (isset($_SESSION['initiated']))
    session_regenerate_id();
else
    $_SESSION['initiated'] = true;
 
// блок 2
if(isset($_SESSION['views']))
    $_SESSION['views']++;
else
    $_SESSION['views'] = 1;
 
// блок 3
echo "<html>
<body>
Количество просмотров: ".$_SESSION['views']."
</body>
</html>";
 
?>

Комментарий

В блоке 1 появляется вызов функции session_regenerate_id с аргументом true, который говорит серверу удалять старые данные сессии при повторной генерации идентификатора. Это вовсе не означает, что данные будут потеряны — они будут доступны с использованием нового идентификатора. При обращении со старым PHPSESSID, сессия восстановлена не будет, а вместо этого будет создана новая.

Примечание. Переменная $_SESSION['initiated'] позволяет отслеживать, была ли сессия только что инициирована или же восстановлена. Проверка if(isset($_SESSION['views'])) необходима для того, чтобы избежать лишней перегенерации идентификатора и отправки двух кук в случае, если сессия только что создана. Если проверки не делать, произойдёт следующее (заголовки ответа сервера):

HTTP/1.1 200 OK
Date: Fri, 30 Sep 2011 07:53:17 GMT
Server: Apache/2.2.13 (Win32) PHP/5.2.10
X-Powered-By: PHP/5.2.10
Set-Cookie: PHPSESSID=6i5cdc8oifooc5vucpn6851ds7; path=/
PHPSESSID=pg3t6eu5hoai97juce4fc1f4i0; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 58
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Первая загрузка скрипта

Заголовки запроса

GET / HTTP/1.1
Host: firingrange.local
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Cache-Control: max-age=0

Заголовки ответа

HTTP/1.1 200 OK
Date: Fri, 30 Sep 2011 09:14:40 GMT
Server: Apache/2.2.13 (Win32) PHP/5.2.10
X-Powered-By: PHP/5.2.10
Set-Cookie: PHPSESSID=fbbjvmj4ie6tm0sb81hp7jim05; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 58
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Комментарий

В исходном запросе браузер не идентифицировал себя никаким образом, поэтому механизм сессий PHP сгенерировал новый уникальный идентификатор сессии и скомандовал браузеру создать куку, в которой будет храниться этот самый идентификатор.

Сторона сервера

В результате работы скрипта на стороне сервера создаётся файл sess_fbbjvmj4ie6tm0sb81hp7jim05 следующего содержания:

initiated|b:1;views|i:1;

Сторона клиента

На стороне клиента создаётся кука PHPSESSID=fbbjvmj4ie6tm0sb81hp7jim05, в которой хранится значение уникального идентификатора сессии.

Результат работы скрипта

Вторая загрузка скрипта

Заголовки запроса

GET / HTTP/1.1
Host: firingrange.local
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Cookie: PHPSESSID=fbbjvmj4ie6tm0sb81hp7jim05
Cache-Control: max-age=0

Заголовки ответа

HTTP/1.1 200 OK
Date: Fri, 30 Sep 2011 09:17:14 GMT
Server: Apache/2.2.13 (Win32) PHP/5.2.10
X-Powered-By: PHP/5.2.10
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: PHPSESSID=tj86ptptn95vla890b1ag70au7; path=/
Content-Length: 58
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Комментарий

Браузер отправляет серверу куку, тот восстанавливает сессию, видит, что установлен флаг $_SESSION['initiated'] и поэтому генерирует новый идентификатор. Браузеру отдаётся команда установить новую куку.

Сторона сервера

В результате работы скрипта на стороне сервера файл sess_fbbjvmj4ie6tm0sb81hp7jim05, содержащий данные сессии, удаляется. Вместо него создаётся новый файл sess_tj86ptptn95vla890b1ag70au7 следующего содержания:

initiated|b:1;views|i:2;

Сторона клиента

На стороне клиента обновляется кука PHPSESSID. Новое значение PHPSESSID= tj86ptptn95vla890b1ag70au7.

Результат работы скрипта

Далее всё повторяется по сценарию второй загрузки скрипта. При попытке использовать один из старых идентификаторов, сервер не найдёт соответствующий файл и будет создана новая сессия.

В работе использовались

  1. Веб-сервер Apache 2.2.13 (http://httpd.apache.org/).
  2. PHP 5.2.10 (http://www.php.net/)
  3. Mozilla Firefox 6.0.2 (http://www.mozilla.org/ru/firefox/).
  4. Плагин Firebug 1.8.3 для просмотра логов передачи данных между браузером и веб-сервером. (Спасибо Adil за подсказку.)
  5. Far Manager (http://www.farmanager.com/).
  6. Веб-сервис Bertal.ru для симуляции воровства кук.
  7. Eclipse PDT 2.2.0 All In Ones для симпатичной подсветки синтаксиса PHP (http://www.eclipse.org/projects/project.php?id=tools.pdt).

Реклама

Комментарии

Владимир

Спасибо! Очень полезная статья все, что называется наглядно и по полочкам. Смущает один момент. При повторной генерации все же есть возможности перехватить идентификатор сессии и использовать в своих коварных целях. Риск конечно снижен за счет постоянного обновления этого идентифекатора, а привязка к ip и юзерагентам будет доставлять неудобства пользователям, но может быть существуют еще более надежные способы засчиты от хака сессий?

2 мая 2014

 

Вам будет также интересно

Синонимы к слову «изнутри»

Все синонимы к слову ИЗНУТРИ вы найдёте на Карте слов.

AP-Fusion - система построения сайтов на основе CMS PHP-Fusion

Легкий и очень быстрый движок на PHP+MySQL. Содержит все необходимое для создания портала, блога или новостного сайта.

Читать далее...

Зачем программистам изучать физику

Многие люди, обучающиеся профессии программиста, искренне не понимают, зачем им нужно изучать такие дисциплины, как физика и математика. В одной из статей мы выяснили, что математика учит думать. Теперь давайте обсудим, зачем люди технических специальностей изучают физику.

Читать далее...

Освоение правил построения канала передачи данных по протоколу TCP/IP в среде МатЛАБ

Цель работы: освоение правил обмена данными по протоколу TCP/IP. Задача работы: построение канала передачи данных по протоколу TCP/IP в среде МатЛАБ. Приборы и принадлежности: Два персональных компьютера c МатЛАБ, Ethernet кабель.

Читать далее...

Программистка (люди, лица, судьбы)

В делах с настойчивым упрямством
Добьется нужного всего.
В границы личного пространства
Не допускает никого...

Читать далее...

Студенты из России завоевали золото на ЧМ по программированию

В понедельник, 30 мая, студенты из России завоевали 5 из 12 медалей командного чемпионата мира по программированию в американском городе Орландо. Об этом сообщается на сайте чемпионата мира.

Читать далее...

Добавить статью

Приглашаем вас добавить статью и стать нашим автором

Поделитесь с друзьями

Статистика

©  Интернет-журнал «Серый Волк» 2010-2016

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