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

Обновлено: 04.07.2024

Казалось бы, вот тебе живая демонстрация незащищенности как беспроводной сети, так и онлайн-сервисов. Но нет. Ведь даже аудитория конференции, подготовленные в своей массе пользователи, с завидным упорством продолжали делиться своими сессиями со сниффером. Но если не так, то как еще показать людям, что открытая сеть несет в себе большую опасность, а онлайн-сервисы, наконец, обязать производить авторизацию пользователя через защищенное соединение? Эрик Батлер, программист из СанДиего, решил так: надо упросить процесс угона сессия донельзя, доведя его буквально до одного клика. Может тогда дойдет?

Задача упрощена настолько, что от пользователя требуется лишь получить доступ к открытой сети и нажать на панели кнопку «Start Capturing». Как только кто-то из сети посетит сайт по незащищенном соединению, Firesheep тут же даст знать, отобразив данные на экране. Проблема может возникнуть с неправильно выбранным сетевым интерфейсом, который задается в настройках плагина.

А боковая панель, где отображаются данные Firesheep, может быть свернута по умолчанию. Но это единственные сложности. Впечатляет та изящность и, не побоюсь этого слова, дерзость, с которой реализована система для вывода данных. Для каждой перехваченной сессии отображается названия и логотип онлайн-сервиса, имя пользователя и даже его аватар. Один клик по нужному элементу — и в новой вкладке браузера будет открыта страница с использованием перехваченных данных жертвы! Вот уж точно, нагляднее не придумать. Сервисы, для которых осуществляется угон сессии, задаются в настройках плагина (вкладка Websites). Для каждого из них здесь написан небольшой JS-скипт, в котором написано, как получить имя пользователя, его аватар и использовать данные перехваченной сессии. Изначально в плагин встроены сценарии для двух десятков популярных западных сервисов, в том числе Twitter, Dropbox, Google, Но если взять за основу эти примеры, за пару минут можно написать скрипт для произвольного сайта. К тому же кнопка в нижней панели позволяет отображать данные перехваченных сессии в чистом виде, без обработки их скриптами.

Степа Ильин

Главный редактор «Хакера» с 2012 по начало 2014 года. Сейчас с командой единомышленников строит компанию Wallarm, разрабатывающую решения для защиты веб-приложений от хакерских атак и обнаружения в них уязвимостей.

ElePHPant. PHP for beginners. Session

Всем хорошего дня. Перед вами первая статья из серии PHP для начинающих разработчиков. Это будет необычная серия статей, тут не будет echo "Hello World" , тут будет hardcore из жизни PHP программистов с небольшой примесью «домашней работы» для закрепления материала.

Изначально подразумевали, что по этому протоколу будет только HTML передаваться, отсель и название, а сейчас чего только не отправляют и =^.^= и(•_ㅅ_•)


А вот пример ответа:

  1. При авторизации пользователя, сервер генерирует и запоминает уникальный ключ — идентификатор сессии, и сообщает его браузеру
  2. Браузер сохраняет этот ключ, и при каждом последующем запросе, его отправляет
Т.е. если украсть cookie из вашего браузера, то можно будет зайти на вашу страничку в facebook от вашего имени? Не пугайтесь, так сделать нельзя, по крайней мере с facebook, и дальше я вам покажу один из возможных способов защиты от данного вида атаки на ваших пользователей.

Давайте теперь посмотрим как изменятся наши запрос-ответ, будь там авторизация:


Метод у нас изменился на POST, и в теле запроса у нас передаются логин и пароль. Если использовать метод GET, то строка запроса будет содержать логин и пароль, что не очень правильно с идеологической точки зрения, и имеет ряд побочных явлений в виде логирования (например, в том же access.log ) и кеширования паролей в открытом виде.

Ответ сервер будет содержать заголовок Set-Cookie: KEY=VerySecretUniqueKey , что заставит браузер сохранить эти данные в файлы cookie, и при следующем обращении к серверу — они будут отправлены и опознаны сервером:

PHP и сессия

Я надеюсь, у вас уже установлен PHP на компьютере, т.к. дальше я буду приводить примеры, и их надо будет запускать
Вот вам статейка на тему PHP is meant to die, или вот она же на русском языке, но лучше отложите её в закладки «на потом».

Перво-наперво необходимо «стартовать» сессию — для этого воспользуемся функцией session_start(), создайте файл session.start.php со следующим содержимым:


Запустите встроенный в PHP web-server в папке с вашим скриптом:

Cookie

Там будет много чего, интересует нас только вот эта строчка в ответе сервера (почистите куки, если нет такой строчки, и обновите страницу):


Увидев сие, браузер сохранит у себя куку с именем `PHPSESSID`:

Browser session cookie

PHPSESSID — имя сессии по умолчанию, регулируется из конфига php.ini директивой session.name, при необходимости имя можно изменить в самом конфигурационном файле или с помощью функции session_name()

И теперь — обновляем страничку, и видим, что браузер отправляет эту куку на сервер, можете попробовать пару раз обновить страницу, результат будет идентичным:

Browser request with cookie

Итого, что мы имеем — теория совпала с практикой, и это просто отлично.

Следующий шаг — сохраним в сессию произвольное значение, для этого в PHP используется супер-глобальная переменная $_SESSION , сохранять будем текущее время — для этого вызовем функцию date():


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


Обновляем — время не меняется, то что нужно. Но при этом мы помним, PHP умирает, значит данную сессию он где-то хранит, и мы найдём это место…

Всё тайное становится явным


По умолчанию, PHP хранит сессию в файлах — за это отвечает директива session.save_handler, путь по которому сохраняются файлы ищите в директиве session.save_path, либо воспользуйтесь функцией session_save_path() для получения необходимого пути.
В вашей конфигурации путь к файлам может быть не указан, тогда файлы сессии будут хранится во временных файлах вашей системы — вызовите функцию sys_get_temp_dir() и узнайте где это потаённое место.

Так, идём по данному пути и находим ваш файл сессии (у меня это файл sess_dap83arr6r3b56e0q7t5i0qf91 ), откроем его в текстовом редакторе:


Как видим — вот оно наше время, вот в каком хитром формате хранится наша сессия, но мы можем внести правки, поменять время, или можем просто вписать любую строку, почему бы и нет:


Для преобразования этой строки в массив нужно воспользоваться функцией session_decode(), для обратного преобразования — session_encode() — это зовется сериализацией, вот только в PHP для сессий — она своя — особенная, хотя можно использовать и стандартную PHP сериализацию — пропишите в конфигурационной директиве session.serialize_handler значение php_serialize и будет вам счастье, и $_SESSION можно будет использовать без ограничений — в качестве индекса теперь вы сможете использовать цифры и специальные символы | и ! в имени (за все 10+ лет работы, ни разу не надо было :)

Напишите свою функцию, аналогичную по функционалу session_decode() , вот вам тестовый набор данных для сессии (для решения знаний регулярных выражений не требуется), текст для преобразования возьмите из файла вашей текущей сессии:

Так, что мы ещё не пробовали? Правильно — украсть «печеньки», давайте запустим другой браузер и добавим в него теже самые cookie. Я вам для этого простенький javascript написал, скопируйте его в консоль браузера и запустите, только не забудьте идентификатор сессии поменять на свой:


Вот теперь у вас оба браузера смотрят на одну и туже сессию. Я выше упоминал, что расскажу о способах защиты, рассмотрим самый простой способ — привяжем сессию к браузеру, точнее к тому, как браузер представляется серверу — будем запоминать User-Agent и проверять его каждый раз:

Добавьте в код проверку на IP пользователя, если проверка не прошла — удалите скомпрометированную сессию.

По шагам

А теперь поясню по шагам алгоритм, как работает сессия в PHP, на примере следующего кода (настройки по умолчанию):

  1. после вызова session_start() PHP ищет в cookie идентификатор сессии по имени прописанном в session.name — это PHPSESSID
  2. если нет идентификатора — то он создаётся (см. session_id()), и создаёт пустой файл сессии по пути session.save_path с именем sess_ , в ответ сервера будет добавлены заголовки, для установки cookie =
  3. если идентификатор присутствует, то ищем файл сессии в папке session.save_path :
    • не находим — создаём пустой файл с именем sess_ (идентификатор может содержать лишь символы из диапазонов a-z , A-Z , 0-9 , запятую и знак минус)
    • находим, читаем файл и распаковываем данные (см. session_decode()) в супер-глобальную переменную $_SESSION (файл блокируется для чтения/записи)
  4. когда скрипт закончил свою работу, то все данные из $_SESSION запаковывают с использованием session_encode() в файл по пути session.save_path с именем sess_ (блокировка снимается)
Задайте в вашем браузере произвольное значение куки с именем PHPSESSID , пусть это будет 1234567890 , обновите страницу, проверьте, что у вас создался новый файл sess_1234567890

А есть ли жизнь без «печенек»?

PHP может работать с сессией даже если cookie в браузере отключены, но тогда все URL на сайте будут содержать параметр с идентификатором вашей сессии, и да — это ещё настроить надо, но оно вам надо? Мне не приходилось это использовать, но если очень хочется — я просто скажу где копать:

А если надо сессию в базе данных хранить?


Для хранения сессии в БД потребуется изменить хранилище сессии и указать PHP как им пользоваться, для этой цели создан интерфейс SessionHandlerInterface и функция session_set_save_handler.
Отдельно замечу, что не надо писать собственные обработчики сессий для redis и memcache — когда вы устанавливаете данные расширения, то вместе с ними идут и соответствующие обработчики, так что RTFM наше всё. Ну и да, обработчик нужно указывать до вызова session_start() ;)
Реализуйте SessionHandlerInterface для хранения сессии в MySQL, проверьте, работает ли он.
Это задание со звёздочкой, для тех кто уже познакомился с базами данных.

Когда умирает сессия?

За время жизни сессии отвечает директива session.gc_maxlifetime. По умолчанию, данная директива равна 1440 секундам (24 минуты), понимать её следует так, что если к сессии не было обращении в течении заданного времени, то сессия будет считаться «протухшей» и будет ждать своей очереди на удаление.

Интересен другой вопрос, можете задать его матёрым разработчикам — когда PHP удаляет файлы просроченных сессий? Ответ есть в официальном руководстве, но не в явном виде — так что запоминайте:

Сборщик мусора (garbage collection) может запускаться при вызове функции session_start() , вероятность запуска зависит от двух директив session.gc_probability и session.gc_divisor, первая выступает в качестве делимого, вторая — делителя, и по умолчанию эти значения 1 и 100, т.е. вероятность того, что сборщик будет запущен и файлы сессий будут удалены — примерно 1%.

Измените значение директивы session.gc_divisor так, чтобы сборщик мусора запускался каждый раз, проверьте что это так и происходит.

Самая тривиальная ошибка

Ошибка у которой более полумиллиона результатов в выдаче Google:

Cannot send session cookie — headers already sent by
Cannot send session cache limiter — headers already sent

Для получения таковой, создайте файл session.error.php со следующим содержимым:

Во второй строке странная «магия» — это фокус с буфером вывода, я ещё расскажу о нём в одной из следующих статей, пока считайте это лишь строкой длинной в 4096 символов, в данном случае — это всё пробелы

Запустите, предварительно удалив cookie, и получите приведенные ошибки, хоть текст ошибок и разный, но суть одна — поезд ушёл — сервер уже отправил браузеру содержимое страницы, и отправлять заголовки уже поздно, это не сработает, и в куках не появилось заветного идентификатора сессии. Если вы стокнулись с данной ошибкой — ищите место, где выводится текст раньше времени, это может быть пробел до символов <?php , или после ?> в одном из подключаемых файлов, и ладно если это пробел, может быть и какой-нить непечатный символ вроде BOM, так что будьте внимательны, и вас сия зараза не коснется (как-же,… гомерический смех).

Для проверки полученных знаний, я хочу, чтобы вы реализовали свой собственный механизм сессий и заставили приведенный код работать:

Для осуществления задуманного вам потребуется функция register_shutdown_function()

Блокировка

Ещё одна распространённая ошибка у новичков — это попытка прочитать файл сессии пока он заблокирован другим скриптом. Собственно, это не совсем ошибка, это недопонимание принципа блокировки :)

Но давайте ещё раз по шагам:

  1. session_start() не только создаёт/читает файл, но и блокирует его, чтобы никто не мог внести правки в момент выполнения скрипта, или прочитать не консистентные данные из файла сессии
  2. блокировка снимается по окончанию выполнения скрипта

«Воткнутся» в данную ошибку очень легко, создайте два файла:

Теперь, если вы откроете в браузере страничку lock.php , а затем в новой вкладке откроете start.php то увидите, что вторая страничка откроется только после того, как отработает первый скрипт, который блокирует файл сессии на 10 секунд.

Есть пару вариантов, как избежать подобного явления — «топорный» и «продуманный».

«Топорный»
Использовать самописный обработчик сессий, в котором «забыть» реализовать блокировку :)
Чуть лучше вариант, это взять готовый и отключить блокировку (например у memcached есть такая опция — memcached.sess_locking) O_o
Потратить часы на дебаг кода в поисках редко всплывающей ошибки…

«Продуманный»
Куда как лучше — самому следить за блокировкой сессии, и снимать её, когда она не требуется:

— Если вы уверенны, что вам не потребуется вносить изменения в сессионные данные используйте опцию read_and_close при старте сессии:

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

— Если вам таки нужно вносить изменения в сессию, то после внесения оных закрывайте сессию от записи:

Чуть выше был приведён листинг двух файлов start.php и lock.php , создайте ещё файлы read-close.php и write-close.php , в которых вы будете контролировать блокировку перечисленными способами. Проверьте как работает (или не работает) блокировка.

В заключение

В этой статье вам дано семь заданий, при этом они касаются не только работы с сессиями, но так же познакомят вас с MySQL и с функциями работы со строками. Для усвоения этого материала — отдельной статьи не нужно, хватит и мануала по приведенным ссылкам — никто за вас его читать не будет. Дерзайте!

Браузер я так понимаю, создает cookie с id юзера в зашифрованном виде (скажем с помощью md5). То есть если генерировать с помощью md5 user_id другой (17 к примеру) и заменить cookie, то получается можно получить доступ к юзеру с id 17? Или я что-то не понимаю?

1,040 11 11 серебряных знаков 18 18 бронзовых знаков Браузер ничего не создаёт. PHP создаёт куку со случайным значением — идентификатором сессии, и файл соответствующий этой куке. Подделать (или украсть) можно только эту куку. @AlexeyTen как понять со случайным значением? то есть он его зашифровывает? Если зашифровать с другим значением доступ получается получить можно если других проверок нету(ip. ) и как php ставит файл, он отдает браузеру, а браузер, я так понимаю ставит. Никто ничего не шифрует. Просто генерируется случайная последовательность символов, которая является идентификатором юзера. Не юзера, а сессии. А что хранится в сессии, PHP вообще не волнует (пока это можно правильно сериализовать/восстановить) Ну да. Только генерировать случайные идентификаторы и надеяться, что попадёшь в существующую сессию. Шансы на это крайне малы. В общем, сессии не подделывают. Их крадут, но это уже совсем другой разговор.

У сервера есть ящик. В ящике лежит user_id = 22. Сервер на ящик прибивает табличку с какой-нибудь аброкадаброй и хранит у себя все время сам ящик, никому не дает его.

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

На самом примитивном уровне концепции "сессия-кука" это действительно так, но на практике такой простой подход уже используется редко - хотя бы потому, что с увеличением "табличек"(пользователей) увеличивается и вероятность их случайного "угадывания" - брутфорс. Многие используют простую модификацию, опирающуюся на второй фактор - например, привязка идентификатора к IP

У каждой PHP сессии уникальный код который нельзя так просто подобрать, а все данные сессии хранятся только на сервере этот "уникальный код" который устанавливается в cookie т.е работает так

Проще говоря можно представить что $_SESSION это файл сессии, user_id ячейка в этом файле, а 22 это значение ячейки user_id

P.s данные сессии не как нельзя получить на стороне пользователя (если их только самому не отдать ему к примеру выводом через echo $_SESSION['user_id']; )


Условно говоря, кука (в данном случае, айдишка в куке) прокидывается к атакующему.

Если кука прокинулась к атакущему, простейший скрипт позволит ему при получении куки автоматически сделать какие-нибудь нехорошие действия, например, зайти под вами в админку, перезаписать какой-нибудь файл, сохранить его, вы даже и не заметите, что произошло. Просто какой-то сервер в интернете за 1-2 секунды авторизовался под вами и сделал все что нужно. Изменил плагин вордпресса, например. А автор заразы в это время спит себе спокойно. Скрипт работает. А он спит. А у тебя сифилис на сайте. И какой-нибудь вредоносный код, заражающий пользователей.

В итоге - сервер заражен, пользователи заражены, "и мы в аду и ты горишь, сынок."

Это одна из причин, почему некоторые сайты не позволяют заходить одновременно с двух ip-адресов. Для идентификации пользователя используется пара id+ip.

И именно поэтому, например, какой-нибудь альфа-банк разлогинивает вас автоматически через, кажется, 10-15 минут неактивности. Пришел к тебе друг, а ты вышел собаку прогулять, а комп забыл залочить. Находясь в одной и той же подсети, например, можно стащить куку, и пара cookie/ip с другого компьютера в той же подсети вполне себе сработает без всякого ip spoofing. (тут я не в курсе, насколько защита от ip spoofing продвинулась за последние несколько лет)

p.p.s. если вы решите создавать куки самостоятельно, при помощи md5 от id пользователя, то не решайте так.

Какой-нибудь милый человек посмотрит сколько у вас примерно пользователей, после этого будет время от времени (в зависимости от количества пользователей) делать запросы с подходящим md5. Если вы еще приделаете список пользователей онлайн - будет еще легче. Про соль вы забудете, про перец забудете и в итоге сессия вашего юзера будет md5(1), md5(2), md5(n). Можно просто сделать md5(1) = авторизован = творю что хочу. Не балуйтесь с крипто, не прочитав про него.

Пользуйтесь встроенными механизмами генерации куки от php/почитайте про шифрование и хэширование.

Браузер который одновременно во вкладах держит разные сессии прокси и куки
Доброго времени суток! У меня появилась одна идея, но для этого мне нужен браузер который способен.

PHP сессии, сортировка массива сессии
Доброе время суток, существует такая ситуация, у меня есть корзина, которая основана на сессиях.

Как "увидеть" именованные объекты ядра (event), созданные в одной сессии, из другой сессии ?
Добрый день! Появилась неожиданная задача: Win 2008 Server, и в нём несколько сессий различных.

Вулкан в Хроме выскакивает периодически. Когда закрыт браузер грузит браузер и открывает рекламу
Доброго времени суток господа. Сначало выскакивал вулкан в гугл хроме, в остальных не наблюдал.

Эта баг фича хрома.
Поробуй перейти в настройки - расширенные настройки и снять чекбокс "не отключать работающие в фоновом режиме сервисы при закрытии браузера" Да. дело еще в том, что все остальные установленные мною сессии, работают правильно. Некоторые я удаляю принудительно. В некоторых я делаю инкремент и после определенного значения они обнуляются. Вот только с этой сессией какая-то беда. И еще. На другом сервере этот код работал. Когда я перешел на новый сервер - началась эта непонятка.
Код простой.
снять чекбокс "не отключать работающие в фоновом режиме сервисы при закрытии браузера"

Супер! Спасибо. А я и не знал про этот чекбокс. Проверю при снятой галочке, а потом скажу результат. Но в любом случае большое спасибо. Честно говоря, уже этот Хром достал. Иногда очень сильно подвешивает комп. хоть я все что мог отключил. Но про это не знал.

Добавлено через 2 часа 28 минут
Не сработало( Закрывал браузер на более часа. Эта сессия не удалилась. Подскажите настройки на сервере. У меня есть доступ по SSH протоколу.
Очень странно, что в работая в других браузерах, такого не происходит. Как браузер вообще может влиять на удаление сессий на сервере?

Тут не все так просто. Есть параметр session.gc_maxlifetime , он определяет когда данную сессию считать "мусором", но чтобы очистить устаревшие сессии должен быть запущен сборщик мусора, а вероятность его запуска вычисляется как session.gc_probability / session.gc_divisor, по умолчанию это 1%. Можно конечно увеличить это значение, но это скажется на производительности.

Есть параметр session.cookie-lifetime, который устанавливает сколько сессионная кука хранится в браузере, но даже уничтожив куку в браузере сессия на сервере не уничтожается, можно самому создать куку с "уничтоженным" id и сессия продолжит работу как ни в чем не бывало.

Если сессии храняться в файлах, то возможно использовать cron для периодического запуска скрипта, который будет удалять устаревшие файлы, но тут сразу возникает проблема когда в 1 месте хранятся сессии разных сайтов с разным session.gc_lifetime. В Debian/Ubuntu так и сделано, там отключен стандартный сборщик установкой session.gc_probability = 0 и есть cron (/etc/cron.d/php5). В качестве времени жизни используется максимальный session.gc_lifetime из всех php.ini, или 24 минуты, если gc_lifetime не найден.

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

Читайте также: