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.
Результат работы скрипта

Далее всё повторяется по сценарию второй загрузки скрипта. При попытке использовать один из старых идентификаторов, сервер не найдёт соответствующий файл и будет создана новая сессия.
В работе использовались
- Веб-сервер Apache 2.2.13 (http://httpd.apache.org/).
- PHP 5.2.10 (http://www.php.net/)
- Mozilla Firefox 6.0.2 (http://www.mozilla.org/ru/firefox/).
- Плагин Firebug 1.8.3 для просмотра логов передачи данных между браузером и веб-сервером. (Спасибо Adil за подсказку.)
- Far Manager (http://www.farmanager.com/).
- Веб-сервис Bertal.ru для симуляции воровства кук.
- 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 медалей командного чемпионата мира по программированию в американском городе Орландо. Об этом сообщается на сайте чемпионата мира.
