Безопасность сессий
Внешние ссылки: » Атака "Фиксация сессии"
Работа с HTTP сессиями является основой web безопасности. Все улучшения должны применяться только убедившись в безопасности сессий. Разработчик должен включать и использовать соответствующие настройки соответствующим образом.
- session.cookie_lifetime=0. У 0 есть специальное значение. Он указывает браузерам не хранить cookie в постоянном хранилище. Тогда, при завершении работы браузера, cookie с сессионным ID сразу же удаляется. Если разработчик устанавливает значение отличное от нуля - это может позволить другим пользователям воспользоваться этим сессионным ID. Большинство приложений должны использовать значение "0". Если требуется функционал автоматической аутентификации, необходимо реализовать отдельный безопасный функционал. Не используйте для этого сессионный ID.
- session.use_cookies=On и session.use_only_cookies=On. Не смотря на то, что у HTTP cookie есть некоторые проблемы, cookie являются предпочтительным способом управления сессионным ID. Если возможно, то используйте только cookie для этих целей. Большинство приложений должны их использовать.
- session.use_strict_mode=On. Запрещает сессионному модулю использовать неинициализированный сессионный ID. Другими словами, сессионный модуль принимает только допустимые ID, созданные им же. Он отбрасывает ID, задаваемые пользователем. Внедрение сессионного ID может осуществляться cookie-инъекцией через JavaScript. При включенной прозрачной обработке сессии, сессионный ID может быть внедрен через строку запроса или параметр формы. Нет причин принимать созданные пользователем сессионные ID, большинство приложений не должны принимать их.
- session.cookie_httponly=On. Запрещает доступ к сессионной cookie для JavaScript. Эта опция предотвращает кражу cookie с помощью JavaScript инъекции. Можно использовать сессионный ID как защитный ключ CSRF, но не рекомендуется. Например, HTML может быть сохранен и отправлен другому пользователю. Разработчик не должен записывать сессионный ID внутри web-страницы для повышения безопасности. Почти все приложения должны использовать атрибут httponly для сессионной cookie.
- session.cookie_secure=On. Позволяет получать доступ к cookie сессионной ID только при использовании протокола HTTPS. Если ваш сайт использует только протокол HTTPS, вам необходимо включить эту опцию. Для таких сайтов нужно также рассматривать использование HSTS.
- session.gc_maxlifetime=[наименьшее возможное]. Сборка мусора производится с некоторой вероятностью. Поэтому эта опция не гарантирует удаление старых сессий. Некоторые модули обработки сохранения сессий не используют эту опцию. Обратитесь к документации по обработке сохранения сессий. Хотя разработчик и не может полагаться на эту опцию, тем не менее рекомендуется установить эту опцию с наименьшим возможным значением. Задайте session.gc_probability и session.gc_divisor так, чтобы истекшие сессии удалялись с необходимой частотой. Если требуется функционал автоматической аутентификации, необходимо реализовать отдельный безопасный функционал. Не используйте для этого сессионный ID.
- session.use_trans_sid=Off. Использование прозрачного управления сессионным ID не рекомендуется. Вы можете использовать его, если необходимо. Однако, отключение прозрачного управления повышает безопасность сессий в целом, убирая возможность инъекции сессионного ID и его кражи.
- session.referer_check=[your originating URL] Если session.use_trans_sid включен, то рекомендуется использовать эту опцию, если это возможно. Это уменьшает риск для инъекции сессионного ID. Если ваш сайт находится по адресу http://example.com/, то установите этой опции значение http://example.com/. Обратите внимание, что при использовании HTTPS, браузер не отправляет referrer заголовок. Таким образом, этот параметр не является достаточно надежным показателем безопасности.
- session.cache_limiter=nocache. Убедитесь, что HTTP контент не закэширован для аутентификационной сессии. Допускается кешировать только не конфиденциальный контент. Иначе содержимым могут воспользоваться. Можно использовать значение "private", если HTTP контент не содержит чувствительные к безопасности данные. Учтите, что "private" может оставлять конфиденциальные данные в общем кэше клиентов. Значение "public" можно использовать только, если HTTP контент вообще не содержит никаких конфиденциальных данных.
- session.hash_function="sha256". Более сложная хэш функция будет создавать более сложный сессионный ID. Хотя хэш коллизии почти не происходят и с MD5 хэшом, тем не менее разработчику лучше использовать функции SHA-2 или новее. Разработчики также могут использовать сложные функции sha384 и sha512
Модуль сессии не позволяет гарантировать, что хранимая информация доступна только пользователю, который создал сессию. Необходимо принять дополнительные меры по защите конфиденциальности сессии, основываясь на ассоциированных с ней данных.
Оценка важности данных, передаваемых в рамках сессии, важна для выбора мер по защите этой информации -- обычно это приводит к ухудшению удобства для конечного пользователя. Например, если необходимо защитить пользователя от простейших методов социальной инженерии, следует включить session.use_only_cookies. В данном случае со стороны пользовательского ПО обязательна поддержка cookie, иначе механизм сессий не будет работать.
Существует несколько способов утечки существующего идентификатора сессии третьим лицам. Такая утечка позволяет злоумышленнику получить доступ ко всем данным, ассоциированным с конкретным идентификатором сессии. Во-первых, передача идентификатора сессии в URL. При переходе на внешний сайт идентификатор сессии пользователя и адрес ресурса могут попасть в статистику переходов данного сайта. Во-вторых, при более активной атаке возможно прослушивание сетевого трафика злоумышленником. Если канал передачи данных не зашифрован, идентификаторы сессии будут переданы в виде простого текста. В таком случае решением является обязательное использование SSL пользователями при доступе к сайту. Для этих целей следует применять HSTS.
С версии PHP 5.5.2 доступна опция session.use_strict_mode. При ее включении и при условии, что модуль сохранения сессий ее поддерживает, неинициализированный сессионный ID отвергается и создается новый. Это защищает от атак, которые принуждают пользователя использовать заранее известный ID. Атакующий может размещать ссылки или отправлять письма, которые содержат сессионный ID. Например http://example.com/page.php?PHPSESSID=123456789 . Если опция session.use_trans_sid включена, то жертва откроет сессию с этим идентификатором. Опция session.use_strict_mode уменьшает этот риск.
Даже при уменьшении риска с помощью session.use_strict_mode атакующий все еще может заставить пользователя использовать уже инициализированную сессию, созданную атакующим. Атакующий создает ее до атаки и поддерживает ее существование.
Cookie с сессионным ID должна устанавливаться с указанием domain, path, httponly и secure. Их приоритетность определяется браузерами. Опираясь на эту приоритетность, атакующий может может установить сессионный ID, который будет использоваться бесконечно. Применение session.use_only_cookies не решает эту проблему. session.use_strict_mode уменьшает риск. session.use_strict_mode=On, не допускает использование неинициализированных сессионных ID. Сессионный модуль создает новый ID каждый раз, как получает неизвестный ID. Это может привести к DoS атаке на жертву, но это лучше, чем компрометация аккаунта.
Использование session.use_strict_mode полезно, но не достаточно для аутентификационной сессии. Разработчик должен использовать также функцию session_regenerate_id() для аутентификации. Функцию session_regenerate_id() нужно вызывать до записи информации об аутентификации в $_SESSION. Только в этом случае функция session_regenerate_id() гарантирует, что только новая сессия содержит данные аутентификации, так как во время процесса аутентификации могут возникнуть ошибки и данные могли бы остаться в старой сессии.
Вызов функции session_regenerate_id() может привести к персональной DoS атаке как и при use_strict_mode=On. Однако, DoS лучше, чем компрометация аккаунта. Пересоздание сессионного ID должно происходить хотя бы при аутентификации пользователя. Пересоздание ID уменьшает риск кражи сессии, поэтому рекомендуется периодически выполнять его. Разработчик не должен полагаться на устаревание сессионного ID. Атакующий может обращаться с сессионным ID жертвы периодически для предотвращения его истекания. Разработчик должен самостоятельно реализовать функционал для истекания старых сессий.
Обратите внимание, что session_regenerate_id() по умолчанию
не удаляет старые сессии. Старая аутентифицированная сессия может оставаться доступной.
Если разработчик хочет предотвратить дальнейшее использование старой сессии,
то он должен уничтожать сессию, установив delete_old_session
в TRUE
. Однако, незамедлительное удаление старых сессий может привести
к неожиданным побочным эффектам. Сессия может быть утеряна в случае
нескольких конкурентных соединений к web-приложению и/или если сетевое
соединение нестабильно. Вместо незамедлительного удаления вы можете установить
маленькое значение времени истекания для $_SESSION. Если пользователь
обращается с устаревшей сессией, отклоните его запрос.
session.use_only_cookies и правильное использование session_regenerate_id() могут привести к персональной DoS. Если такое происходит, то вы можете попросить пользователя удалить cookie и предупредить его о возможных проблемах с безопасностью. Атакующий может устанавливать вредные cookie через уязвимость в web-приложении (т.е. JavaScript инъекция), уязвимость в браузерном плагине и т.д.
Разработчикам не рекомендуется использовать долгоживущие сессионные ID для автоматического входа пользователей из-за повышения риска кражи сессий. Автоматический вход должен реализовываться самим разработчиком. Используйте одноразовые хэш ключи безопасности как ключи для автоматического входа через cookie. Используйте хэши сложнее чем SHA-2, т.е. SHA-256 и сложнее с использованием случайных данных из /dev/urandom и т.д. Если пользователь не аутентифицирован, проверьте верный или нет у него одноразовый хэш. Если ключ верен, аутентифицируйте пользователя и задайте новый одноразовый хэш. Ключ для автоматического входа является долгоживущим ключом, этот ключ должен быть максимально защищенным. Используйте атрибуты path/httponly/secure для защиты cookie. Разработчик должен реализовать функционал, который может выключать автоматический вход и удалять ненужные ключи из cookie.
Коментарии
It is also quite important to (somehow) make sure that the cookies you're setting (including the session cookie) is only visible to the site that created it (or to other trusted sites only).
If the cookie's path is set to '/' (the whole domain), then any website on the same domain (might be lots of websites) _will_ get the cookie through HTTP headers and could possibly hijack your session.
One slightly acceptable protection would be to lock a session to one IP adress.
Perhaps, you would also like to timeout a session after some idle time. I noticed that session.gc_maxlifetime is not suitable for this. So I used this code to do the job:
<?php
if (!isset($_SESSION['timeout_idle'])) {
$_SESSION['timeout_idle'] = time() + MAX_IDLE_TIME;
} else {
if ($_SESSION['timeout_idle'] < time()) {
//destroy session
} else {
$_SESSION['timeout_idle'] = time() + MAX_IDLE_TIME;
}
}
?>
Websites which have sensitive information need to be patched to ensure its not exploited because of session issues.
In earlier versions of apache cookie reliability was not assumed and hence the default method was always using url-rewrite which meant every url link, every form submission etc would have a PHPSESSID=<sessionid> passed along to inform the server about the active session. New versions have turned this off using
session.use_trans_sid = 0
in the /etc/php5/apache2/php.ini file.
Reasons?
Well one might safe the offline page as a bookmark or pass the link across to others not realizing that the session id information is also sent. So someone who quickly accesses these pages could possible get logged on, this was also true wrt search engines, and I guess in some cases it being seen as duplicate content as the same page will have a different session id every time the robots scan the website.
But having this set does not mean you are protected. Let me explain.
What prevents me from presetting the session id! Assume there is a banking site www.example.com which has a login screen at www.example.com/login.php
I can send you can email with a link to the bank site as http://www.example.com/login.php?PHPSESSID=12345
When you click on the link it presents the session id as 12345 rather then asking the server to generate a new one. This is called session fixation. Keep in mind even with session.use_trans_sid = 0 this will work as this sets it only not to use url-rewrite. To prevent this altogether set session.use_only_cookies = 1 which ensures that only cookies will be used, but this could cause problems when dealing with transaction which involve switch sites, i.e. siteA forwards to site B for payment which forwards to siteA for thank you, in which case a phpsessid inform might be used to revive the old session.
A good approach would always be to at the login screen and immediately post login to force a new session id generated using random numbers
session_start();
$newsessid = somerandomnumberfunction();
session_id($newsessid);
you can also use session_regenerate_id() function to generate a new id
session_start();
session_regenerate_id();
Also its always good to ensure every valid session is checked against an ip. One good method is to store the session id and remote ip information in a table, or better store the ip as a session variable itself, once the user logs in and ensure that this is continued for remaining pages for security. This ofcourse wont work when users use the same office or shared network as the ip to the outside world is the same.
https is always a good idea for sensitive sites, but keeping it persistent for all pages which use session is important if you really want a foolproof system else anyone can always sniff your packets.
So to quickly go through the bits
- set session.use_trans_sid = 0 in /etc/php5/apache2/php.ini file.
- Ensure you always use a new self generated session id on successful login attempt.
- Try setting session.use_only_cookies = 1 and check if all works fine.
- Use https throughout to ensure no one can sniff your session id.
- Store session id, remote IP information and compare for successive pages
IP checking is a sometimes useful feature with two limitations that are important to be aware of:
1. Anyone surfing behind a proxy (e.g., someone at work) will provide the proxy's IP, not his own. Session ID replay attacks will not be prevented by IP checking for an attacker on the user's side of the proxy.
2. If the PHP application is behind a reverse proxy, the reverse proxy's IP address will be the only request IP seen by PHP, so IP checking is useless.
In addition to ip-address binding not always being effective, it can also prevent users connecting through a proxy-pool from even being able to use your site. In such a scenario, a person's IP address may very well change with every access.
If you're handling anything remotely secure, the only safe option is HTTPS. If the data doesn't need to be that secure, than you should not allow a high-jacked session to do too much damage. Basically, don't assume that a person really is who they pretend to be just because the session says a person authenticated with a username and password: it may have been that person who logged in, but that doesn't mean it's still that person. So if you're going to do something like change passwords or something, require them to authenticate again on the same form.
Of course this needs to be done in some secure way, as well, so that the password is not just floating over the network. A good way to do this is sending a nonce (number-used-once) along with the form and some javascript to concatenate the nonce to the password, then perform a cryptographic hash of the combined string. The resulting valid-once "password" should then be sent instead of the plain text password. To be effective, you also need to prevent people from using the form if they don't have JavaScript enabled. You can do this by disabling the form fields in HTML, then re-enabling them in JavaScript.