Защита идентификатора сессий в PHP
Безопасность веб-сайтов основывается на управлении сессиями. Когда пользователь подключается к безопасному сайту, он предоставляет учетные данные, как правило, в форме имени пользователя и пароля. Веб-сервер не имеет представления о том, какой пользователь уже вошел в систему и как он переходит от страницы к странице. Механизм сессий позволяет пользователям не вводить пароль каждый раз, когда они хотят выполнить новое действие или перейти к новой странице.
В сущности, управление сессией гарантирует, что в настоящее время соединен тот пользователь, который проходил авторизацию. Но, к сожалению, сессии стали очевидной мишенью для хакеров, поскольку они могут позволить получить доступ к веб-серверу без необходимости проверки подлинности.
После аутентификации пользователя, веб-сервер предоставляет ему идентификатор сессии. Этот идентификатор хранится в браузере и подставляется всякий раз, когда нужна проверка подлинности. Это позволяет избежать повторяющихся процессов ввода логина/пароля. Все это происходит в фоновом режиме и не доставляет дискомфорта пользователю. Представьте, если бы вы вводили имя и пароль каждый раз, когда просматривали новую страницу!
В данной статье я постараюсь изложить все известные мне способы защиты идентификатора сессии в PHP.
Использование cookie
По умолчанию вся информация о сессии, включая ID, передается в cookie. Но так бывает не всегда. Некоторые пользователи отключают cookie в своих браузерах. В таком случае браузер будет передавать идентификатор сессии в URL.
Здесь ID передается в открытом виде, в отличие от сессии через cookie, когда информация скрыта в HTTP-заголовке. Самым простым способом защиты от этого будет запрет передачи идентификатора сессии через адресную строку. Сделать это можно, прописав следующее в конфигурационном файле Apache-сервера .htaccess:
php_flag session.use_only_cookies on
Использование шифрования
Если на вашем сайте должна обрабатываться конфиденциальная информация, такая как номера кредитных карт (привет от Sony), следует использовать SSL3.0 или TSL1.0 шифрование. Для этого при установке cookie следует указывать true для параметра secure.
Если вы храните пароль сессии в переменной $_SESSION (все-таки лучше использовать sql), то не стоит хранить его в открытом виде.
if ($_SESSION['password'] == $userpass) < // код >
Приведенный выше код не безопасный, так как пароль хранится в виде обычного текста в переменной сессии. Вместо этого используйте md5-шифрование, примерно так:
if ($_SESSION['md5password'] == md5($userpass)) < // код >
Проверка браузера
Чтобы отсечь возможность использования сессии с другого браузера (компьютера), следует ввести проверку поля HTTP-заголовка user-agent:
session_start(); if (isset($_SESSION['HTTP_USER_AGENT'])) < if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT'])) < // код >> else
Срок действия сессии
Ограничьте время жизни сессии, а также время действия cookie. По умолчанию срок действия сессии 1440 секунд. Изменить это значение можно через php.ini и .htaccess. Пример для .htaccess:
# Время жизни сессии в секундах
php_value session.gc_maxlifetime 3600
# Время жизни куки в секундах
php_value session.cookie_lifetime 3600
Привязка по IP-адресу
В определенных ситуациях (не всегда) следует установить привязку по IP-адресу. В основном когда количество пользователей ограничено и имеют статичные IP. Проверка может быть либо по списку разрешенных IP-адресов,
include ("ip_list.php"); //$ip_white_list = array ( 'admin1' => '111.222.333.444', 'admin2' => '555.666.777.888'); if(!empty(array_search($_SERVER['REMOTE_ADDR'],$ip_white_list))) < header("Location: admin.php"); >else
либо по IP-адресу для каждого запроса (только для статичных IP):
if(isset($_SESSION['ip']) and $_SESSION['ip'] == $_SERVER['REMOTE_ADDR']) < header("Location: admin.php"); >else
Следует осознавать, что полностью избежать взлома невозможно. Можно только максимально усложнить этот взлом любыми известными способами. Однако следует также не забывать о своих легальных пользователях, чтобы не осложнить им жизнь такой защитой.
Работа с сессиями в PHP
Сессия, механизм php, созданный для возможности передачи данных предназначенных конкретному пользователю при повторных запросах (веб-сервер не поддерживает постоянного соединения с клиентом, и каждый запрос обрабатывается, как новый, без какой-либо связи с предыдущими).
Принцип работы сессий: сервер выдает браузеру уникальный идентификатор, и просит передавать его с каждым запросом. Передача происходит стандартными способами, либо через куки, либо через переменные POST/GET.
Идентификатор сессии — это обычная переменная, по умолчанию ее имя — PHPSESSID. Можно изменить директивой session.name в php.ini.
На сервере за передачу информации о сессиях отвечают две настройки в php.ini:
- session.use_cookies — если равно 1, то PHP передает идентификатор в куках, если 0 — то нет.
- session.use_trans_sid — если равно 1, то PHP передает его, добавляя к URL и формам, если 0 — то нет.
Соответственно, если включена только первая настройка и браузер отдает куки, то идентификатор передается через них, если не отдает, то сессия обнуляется при каждом запросе.
Если включена только вторая, то PHP дописывает к каждой относительной ссылке и к каждой форме передачу идентификатора сессии, примерно так:
Если включены обе, то браузеру выставляется кука, а ссылки и формы дополняются только если кука найдена не была.
Вся информация о сессии храниться в глобальном массиве $_SESSION.
Запись данных в сессию работает так:
// запускаем новую, либо возобновляем существующую сессию session_start(); // передаем в массив сессий переменную с названием test и данными Hello world $_SESSION['test']='Hello world!'; // если в качестве имени переменной хотим использовать значение переменной - // пишем без кавычек или используем двойные $var = name; $_SESSION["$var"]='Hello world!';
// обычное условие проверки if(!$_SESSION[$var]){ echo "session variable is empty" } // можно получить id текущей сессии или ее имя session_id(); session_name();
Удаление переменных из сессии:
unset($_SESSION[$var]); // Если register_globals = on, надо добавить строку session_unregister($var); //Если надо сбросить все переменные сессии session_unset();
Для закрытия сессии используется функция:
Данные из глобального массива $_SESSION php хранит либо в файлах, путь к которым указывается в session.save_path в php.ini, либо в БД.
Для управления HTTP-заголовками отвечающими за кэш, используется функция session_cache_limiter(). Установка nocache, например, отменяет кэширование на стороне клиента.
Во время начала запроса режим кеширования сбрасывается до значения по умолчанию, хранящегося в session.cache_limiter. Таким образом, вам необходимо вызывать session_cache_limiter() для каждого запроса (перед тем, как вызвана функция session_start()).
Значение | Посылаемый заголовок |
---|---|
public | Expires: (когда-нибудь в будущем, в зависимости от session.cache_expire) Cache-Control: public, max-age=(когда-нибудь в будущем, в зависимости от session.cache_expire) Last-Modified: (временная метка последнего сохранения сессии) |
private_no_expire | Cache-Control: private, max-age=(session.cache_expire в будущем), pre-check=(session.cache_expire в будущем) Last-Modified: (временная метка последнего сохранения сессии) |
private | Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: private, max-age=(session.cache_expire в будущем), pre-check=(session.cache_expire в будущем) Last-Modified: (временная метка последнего сохранения сессии) |
nocache | 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 |
Возможные проблемы
- Вспомогательными вещами, вроде кодирования данных и удаления старых сессий, php занимается сам, и если возникает проблема с удалением информации о них, проверьте в php.ini строку session.gc_probability. Для того чтобы php мог самостоятельно удалять файлы сессий, должно быть установлено 1.
- Warning: open(/tmp\sess_SID, O_RDWR) failed: No such file or directory (2) in full_script_path on line number
или
Warning: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/tmp))
в этом случае надо в php.ini, в параметре session.save_path, указать правильный каталог, который существует и доступен для записи (не забудьте перезагрузить апач). - Warning: Cannot send session cookie — headers already sent.
Warning: Cannot send session cache limiter — headers already sent.
Warning: Cannot add header information — headers already sent.
эти ошибки возникают в том случае, если браузер ранее уже получил заголовки для страницы. Функции header(), session_start(), setcookie() и вся логика, которая их вызывает, должны обрабатываться до любого вывода в браузер. - Если давать переменным скрипта имена, совпадающие с индексами массива $_SESSION, возможны проблемы. При register_globals=on значения будут перезаписывать друг друга. При register_globals=off, в случае, если в скрипте есть переменная сессии не имеющая значения, и глобальная переменная с тем же именем, появится ошибка «Your script possibly relies on a session side-effect which existed until PHP 4.2.3.». Для предотвращения этой ошибки, надо инициализировать переменные перед использованием или проверять на существование, и стараться не давать глобальным переменным имена, совпадающие с индексами массива $_SESSION.
- Если вы используете перенаправление через header или навигацию с помощью JavaScript, PHP не пропишет необходимый идентификатор, т.к. он работает только со статичными ссылками. В этом случае надо проставлять идентификатор самостоятельно:
header("Location: /script.php?".session_name().'='.session_id());
Более детальный обзор можно найти на сайте phpfaq.ru