WebScript.Ru
C:\   главная  ::   о сайте  ::  каталог скриптов  ::  гнездо  ::  форум  ::   авторам  :: Новостройки ::   ХОСТИНГ  ::

|| разделы::
|| поиск по сайту::

|| реклама::
|| новости почтой::
Рассылки Subscribe.Ru ::



Новости сайта WebScript.Ru
Популярные статьи

Hot 5 Stories

|| рекомендуем::




Сессии в PHP


Прислал: Алексей Куликов [ 12.03.2002 @ 12:54 ]
Раздел:: [ Статьи по PHP ]


В разнообразных конференциях, посвященных программированию меня в первую очередь всегда интересуют такие разделы, как "Web-программирование" и "Скрипты". По большей части, вопросы о PHP в таких форумах довольно простые, требующие лишь общего понимания PHP, тем не менее, самый часто задаваемый вопрос по моим наблюдениям, это: "Что такое сессии в PHP и с чем/как их можно кушать?". Хотелось бы разъяснить этот вопрос раз и навсегда.

С самого начала PHP все приняли на ура, но как только на этом языке стали создавать достаточно крупные проекты, разработчики столкнулись с новой проблемой – в PHP отсутствовало понятие глобальных переменных! То есть, выполнялся некий скрипт, посылал сгенерированную страницу клиенту, и все ресурсы, используемые этим скриптом уничтожались. Попробую проиллюстрировать: предположим есть две страницы одного сайта, index.php и dothings.php. Исходники к этим страницам выглядят так:

- index.php -
<?php
$a = "Меня задали на index.php";
?>
<html><body>
<?php
echo $a;
?>
</body></html>
- dothings.php -
<html><body>
<?php
echo $a;
?>
</body></html>

Если выполнить эти два скрипта, то на первой странице мы увидим надпись "Меня задали на index.php", а вторая страница будет пустой.

Разработчики web-сайтов, недолго думая, стали использовать cookie для хранения глобальных переменных на стороне клиента. Процесс выглядел примерно так: пользователь приходит на главную страницу сайта, делает какие-то действия, и вся информация, связанная с этим пользователем, которая может потребоваться на других страницах сайта, будет храниться у него в браузере в виде cookie. Этот метод имеет довольно серьезные минусы, из-за которых от PHP в своё время отвернулось немало разработчиков. Например, нам нужно авторизовать пользователя, чтобы разрешить ему доступ к закрытым (или принадлежащим только ему) разделам сайта. Придёться «кидать» пользователю cookie, который будет служит его последующим идентификатором на сайте. Такой подход становится очень громоздким и не удобным, как только сайт начинает собирать всё больше и больше сведений о поведении пользователя, ведь всю информацию, посылаемую пользователю, желательно кодировать, чтобы её нельзя было подделать. Ещё совсем недавно подделкой cookie можно было «повалить» не один чат, а порой и пробраться в чужую почту. К тому же есть ещё на свете странные люди, у которых браузер cookie не поддерживает.

При использовании сессий вся информация хранится не на стороне клиента, а на стороне сервера, и потому лучше защищена от манипуляций злоумышленников. Да и работать с сессиями куда проще и удобнее, так как все данные автоматически проходят через алгоритмы криптографии модуля PHP. В броузере клиента, лишь хранится уникальный идентификатор номера сессии, либо в форме cookie, либо в виде переменной в адресной строке броузера, какой из двух способов использовать для передачи идентификатора сессии между страницами интерпретатор PHPвыбирает сам. Это на 100% безопасно, так как идентификатор сессии уникален, и подделать его практически не возможно (об этом чуть далее, в разделе о безопасности сессий).

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

Как работать с сессиями?

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

"Warning: open(/var/state/php/sess_6f71d1dbb52fa88481e752af7f384db0, O_RDWR) failed: No such file or directory (2)".

Это значит всего лишь, что у вас неправильно настроен PHP. Решить эту проблему можно, прописав правильный путь (на существующую директорию) для сохранения сессий в файле php.ini и перезапустить сервер.

Любой скрипт, который будет использовать переменные (данные) из сессий, должен содержать следующую строчку:

session_start();

Эта команда говорит серверу, что данная страница нуждается во всех переменных, которые связаны с данным пользователем (браузером). Сервер берёт эти перемнные (из файла, либо из БД) и делает их доступными. Очень важно открыть сессию до того, как какие-либо данные будут посылаться пользователю; на практике это значит, что функцию session_start() желательно вызывать в самом начале страницы, например так:

<?php
session_start();
?>
<html>
<head>
</head>
...

После начала сессии можно задавать глобальные переменные. Это элементарно: вызываем функцию session_register('var_name'); и переменная $var_name становится доступной на всех страницах, использующих сессию. Для примера поковыряем программку, приведенную в начале статьи:

- index.php -
<?php
// открываем сессию
session_start();
// задаём значение переменной
$a = "Меня задали на index.php";
// регистрируем переменную с открытой сессией
// важно: названия переменных передаются функции session_register()
// без знака $
session_register("a");
?>
<html>
<body>
Всё ОК. Сессию загрузили!
Пройдём, посмотрим что <a href="dothings.php>там…</a>
</body>
</html>
- dothings.php -
<?php
// открываем сессию
session_start();
?>
<html>
<body>
<?php
echo $a;
?>
</body>
</html>

При запуске этих файлов (в логической последовательности конечно), первый скрипт (index.php) выдаст следующий результат:

Всё ОК. Сессию загрузили! Пройдём, посмотрим что там…

А второй (dothings.php) вот это:

Меня задали на index.php

Переменная $a теперь доступна на всех страницах данного сайта, которые запустили сессии.

Другие полезные функции для работы с сессиями:

·        session_unregister(string) – сессия «забывает» значение заданной глобальной переменной;

·        session_destroy() – сессия уничтожается (например, если пользователь покинул систему, нажав кнопку «выход»);

·        session_set_cookie_params(int lifetime [, string path [, string domain]])–с помощью этой функции можно установить, как долго будет «жить» сессия, задав unix_timestampопределяющий время «смерти» сессии. По умолчанию, сессия «живёт» до тех пор, пока клиент не закроет окно браузера.

Примеры

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

Авторизация Пользователя

Вопросы по авторизации пользователей с помощью PHP-сессий постоянно задаются в конференциях по web-программированию. Механизм авторизации пользователей в системе с помощью сессий довольно хорош с точки зрения безопасности (см. раздел «Безопасность» ниже).

Наш пример будет состоять из трёх файлов: index.php, authorize.php и secretplace.php. Файл index.php содержит форму, где пользователь введёт свой логин и пароль. Эта форма передаст данные файлу authorize.php, который в случае успешной авторизации допустит пользователя к файлу secretplace.php, а в противном случае выдаст сообщение об ошибке.


Приступим: - index.php -
<html>
<head>
<title>Введи пароль, смертный</title>
</head>
<body>
<form action="authorize.php" method="post">
Логин:<input type="text" name="user_name"><br>
Пароль:<input type="password" name="user_pass"><br>
<input type="submit" name="Submit">
</form>
</body>
</html>
- authorize.php -
<?php
// открываем сессию
session_start();
// данные были отправлены формой?
if($Submit){
// проверяем данные на правильность... в данном случае я
// вписал имя пользователя и пароль прямо в код, целесообразней
// было бы проверить логин/пароль в базе данных и при сов-
// падении дать доступ пользователю...
if(($user_name=="cleo")&&($user_pass=="password")){
$logged_user = $user_name;
// запоминаем имя пользователя
session_register("logged_user");
// и переправляем его на «секретную» страницу...
header("Location: secretplace.php");
exit;
}
}
// если что-то было не так, то пользователь получит сообщение об ошибке.
?>
<html><body>
Вы ввели неверный пароль!
</body></html>
- secretplace.php -
<?php
// открываем сессию
session_start();
/*
просто зайти на эту страницу нельзя... если
имя пользователя не зарегистрировано, то
перенаправляем его на страницу index.php
для ввода логина и пароля... тут на самом деле
можно много чего сделать, например запомнить
IP пользователя, и после третьей попытки получить
доступ к файлам, его закрыть.
*/
if(!isset($logged_user)){
header("Location: index.php");
exit;
}
?>
<html>
<body>
Привет, <?php echo $logged_user; ?>, ты на секретной странице!!! :)
</body>
</html>

Безопасность

Итак, мы умеем передавать идентификатор от одной страницы (PHP-скрипта) к другой (до следующего вызова с нашего сайта), а значит мы можем различать всех посетителей сайта. Так как идентификатор сессии – это очень большое число (128 бит), шансов, что его удастся подобрать перебором, практически нет. Поэтому злоумышленнику остаются следующие возможности:

·        на компьютере пользователя стоит «троян», который ворует номера сессий;

·        злоумышленник отлавливает трафик между компьютером пользователя и сервером. Конечно, есть защищенный (зашифрованный) протокол SSL, но им пользуются не все;

·        к компьютеру нашего пользователя подошел сосед и стащил номер сессии.

Такие ситуации, основанные на том, что кто-то что-то у кого-то стащит, в общем, не входят в компетенцию программиста. Об этом должны заботиться администраторы и сами пользователи.

Впрочем, PHP очень часто можно «обмануть». Давайте рассмотрим возможные точки взлома в программе авторизации пользователя:

·        Файл authorize.php – попытка подбора пароля с помощью стороннего скрипта;

·        Файл secretplace.php – попытка обмануть программу путём вписывания значений переменной $logged_user в адресной строке браузера, напримертак:
//www.yoursite.ru/secretplace.php?logged_user=hacker

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

Как «залатать» дыру номер 1?

Не будем писать тонны кода по блокировке IP-адреса и т.п., а просто проверим, откуда приходит запрос, а точнее с какой страницы пришёл запрос, если это будет любая страница с нашего сайта, то всё нормально, а во всех остальных случаях пускать не будем. Подкорректируем файл authorize.php:

- authorize.php V2 -
<?php
// открываем сессию
session_start();
// полный путь к корневой директории где расположены скрипты
$SERVER_ROOT = "//localhost/test1/";
// если пользователь пришёл с любой страницы нашего сайта
// то он вроде наш...
// Переменная $HTTP_REFERER всегда доступна по умолчанию
// и содержит полный адрес ссылающейся страницы...
// функция eregi() проверяет, начинается ли адрес ссылающейся страницы
// со значения в переменной $SERVER_ROOT
if(eregi("^$SERVER_ROOT",$HTTP_REFERER)){
// данные были отправлены формой?
if($Submit){
// далее все как раньше
if(($user_name=="cleo")&&($user_pass=="password")){
$logged_user = $user_name;
// запоминаем имя пользователя
session_register("logged_user");
// и переправляем его на «секретную» страницу...
header("Location: secretplace.php");
exit;
}
}
}
?>
<html><body>
Вы ввели неверный пароль!
</body></html>

Как избавиться от «дыры» номер 2?

Предположим, у вас есть сайт, где каждый смертный может зарегистрироваться чтобы добавлять сообщения в форум. Естественно, в форуме у некоторых пользователей (админов, модераторов), возможностей больше чем у других, они, например, могут удалять сообщения других пользователей. Уровень доступа пользователя вы храните в сессии, в переменной $user_status, где $user_status = 10 соответствует полному доступу к системе. Пришедшему на сайт злоумышленнику достаточно зарегистрироваться штатным образом, а потом дописать в адресной строке браузера ?user_status=10. Вот и завёлся у вас на форуме новый админ!

В принципе, любую переменную скрипта можно задать через адресную строку, просто дописав после полного адреса к скрипту вопросительный знак и название переменной с её значением. Давайте поправим наш код, чтобы этого избежать:

-secretplace.php V2 -

<?php
// убираем всё лишнее из адресной строки
// функция unset() «освобождает» переменную
unset($logged_user);
// открываем сессию
session_start();
// и корректируем испорченные перменные.
// Важно: в этом случае, переменная регистрируется не как новая
// переменная, а как уже существующая, а потому знак $ не опускается
session_register($logged_user);
/*
просто зайти на эту страницу нельзя... если
имя пользователя не зарегистрировано, то
перенаправляем его на страницу index.php
для ввода логина и пароля... тут на самом деле
можно много чего сделать, например запомнить
IP пользователя, и после третьей попытки получить
доступ к файлам, его перекрыть.
*/
if(!isset($logged_user)){
header("Location: index.php");
exit;
}
?>
<html>
<body>
Привет, <?php echo $logged_user; ?>, ты на секретной странице!!! :)
</body>
</html>

Итоги

Механизм сессий – довольно удачная особенность языка PHP. Сессии просты, очень гибки в использовании. Кстати, есть одна, мало где документированная возможность сессий PHP (доступна начиная с версии 4.0.3) – в сессиях можно хранить не только переменные, но и объекты.

Добавление от 14.12.2001

С выходом в свет PHP 4.1.0 – работа с сессиями значительно облегчилась. Все переменные сессий стали доступны из глобального массива _SESSION['var_name']. Самое приятное наверное в том, что при присвоении какого-либо значения любому полю массива, переменная с таким же именем автоматически регистрируется, Сохрани в закладки как переменная сессии, на пр:

<?
$_SESSION['counter'] = 12;
echo $counter;
?>
выведет на экран броузера число 12.



 :::::  alan пишет 12.03.2002 @ 14:07 
Круто это есть...
Заходи в гости http://freephp.dax.ru/
 :::::  Ziko пишет 15.03.2002 @ 18:58 
Пара вопросов: если я куки отключу, то меня пропустят? И еще не совсем понятны преимущества, изложенные в "Добавлении", если можно, поясните, пожалуйста подробнее.
 :::::  Dolce пишет 15.03.2002 @ 19:34 
1) Конечно, как и написано в статье, способ передачи идентификатора сессии, PHP по умолчанию выбирает сам...

2) фишка вся в том, что больше не надо вызывать функцию session_register() для того чтобы помустить переменную с сессию, достаточно просто добавить её в массив.
 :::::  cypress пишет 26.03.2002 @ 00:43 
Что значит выбирает сам? Нифига он сам не выбирает без --enable-trans-sid
 :::::  Dolce пишет 26.03.2002 @ 02:11 
Я предполагаю что PHP настроен правильно, на хорошем коммерческом хостинге.
 :::::  Bum пишет 01.04.2002 @ 22:06 
Неужели трудно подделать $HTTP_REFERRER?
 :::::  Magz пишет 03.04.2002 @ 11:23 
Все круто, но... Самая большая "дырка" здесь в файле index.php - когда передаются пароль и имя пользователя методом POST. "Летят" они по просторам интернета в незащищенном виде, поэтому перехватить их не составит труда. А дальше все описанные защиты отдыхают!
 :::::  Phoenix пишет 29.04.2002 @ 23:26 
А как еще посылать пароль с login'ом????
 :::::  Tweak.tut.by пишет 30.04.2002 @ 20:27 
POST
 :::::  Дмитрий пишет 15.08.2002 @ 16:05 
Почему-то вариант без дыр у меня работает коряво.
Для того чтобы скрипт authorize.php "увидел" переменную $logged_user надо сначала при аутентификации ввести неверные данные, либо два раза проходить аутентификацию. В чем причина я так и не нашел.
Интересно, а у других все нормально работает?
 :::::  Andrew пишет 22.10.2002 @ 15:34 
Во-первых проверь, что у тебя передаётся вообще - print_r ($GLOBALS);
Во - вторых - правильно-ли ты прописал $SERVER_ROOT = (где у тебяскрипт лежит)
Всё! У меня работает!
 :::::  МеХаНиК пишет 14.11.2002 @ 12:01 
На мой взгляд, заплатка по второй дыре могет быть гораздо проще. А именно: использовать (хотя бы при проверке) не переменную $logged_user, а переменную из глобального массива $HTTP_SESSION_VARS['logged_user'] или $_SESSION['logged_user'], что, впрочем, одно и то же. Ведь в этом случае, переменная, пршедшая через строку запроса, будет являться элементом совсем другого массива ($_GET['looed_user'] || $HTTP_GET_VARS['looed_user']).
 :::::  Dolce пишет 14.11.2002 @ 14:32 
На момент написания статьи, глобального массива $_SESSION не существовало, а прописывать полный путь было просто неудобно =)
 :::::  ZZ пишет 26.11.2002 @ 16:01 
Защититься от сниффинга passwd/login достаточно просто - юзайте открытый логин и к нему passwd, зашифрованный по HMAC-MD5 на стороне клиента (типа md5.js - его есть в сети) в обычной форме. Стандартная аутотентификация по сигналу 400/401 не катит - там шифровка по base64 для пионэров и домохозяек. Недостатков два - нужно хранить все HMAC, посланные клиенту, в базе и проверять их на дублирование (отлуп при попытке повторного использования); passwd при регистрации генерить сервером и пересылать мылом. Кул хаккеры отдыхают - проверено.
 :::::  Doctor[X] пишет 07.02.2003 @ 10:11 
Всё это конечно замечательно, но вот почему на локалке, всё это допустимо?
Я попробовал пробить скрипт secretplace.php?logged_user=0
он проходит только так и не какой защиты :(
 :::::  Silver Corvin пишет 22.03.2003 @ 08:47 
Вам мозги на что?
Человек дал движок. Все!
 :::::  zoom пишет 19.06.2003 @ 16:11 
скажите, а для SSL вопросы защиты по поводу перебора пароля про скрипту актуальны?

 :::::  Nova пишет 08.08.2003 @ 13:31 
Вообще храните в сесси масив скажем
$s['login'] = "bla bla bla";
$s['password'] = "123";
session_register("s");
И тогда через строку браузера и.т.д не задашь переменные =) А Рефери подделать оч. просто =) Лучше если уж так надо то в ту же сессию писать откуда юзер идёт или в кукки или ещё как =) Вообщем думайте и дирзайте =)
 :::::  rj пишет 26.02.2004 @ 18:49 
Ничего статья, молодец Алексей
давно хотел с етим разобраться, вот времени не хватало.
Единственное, советую всё-таки в php.ini кидать строчку register_globals = Off

 :::::  George пишет 20.04.2004 @ 15:20 
есть вопросы, касающиеся кнопок броузера F5 - refresh, forward() и back().
Например:
1. как застраховаться от передачи логина и парля по нажатию F5 в возможно старом запросе с этими полями в post?
2. как обеспечить корректную работы такого случая-
пользователь залогинился, нажал кнопку- назад и ушел.
Пришел я и нажал кнопку вперед, залогинясь под его паролем?

Какие идеи?
 :::::  George пишет 20.04.2004 @ 17:46 
вопрос #2 снят - надо корректно прибивать сессии по "back". А #1 - остался открытым

 :::::  ram пишет 10.06.2004 @ 05:59 
Если кто-то действительно хочет посмотреть как работать с сессиями в PHP то советую посмотреть phpBB файл /includes/sessions.php отлично написано, и вообще в phpBB много здравых идей советую! (тот же обработчик ошибок или модуль работы с базами данных)
 :::::  Ugil пишет 10.09.2004 @ 12:08 
Ребята этот скрипт работать не будет, особенно с исправлением второй ошибки )))

 :::::  Ugil пишет 10.09.2004 @ 13:39 
ПРоверил. там чушь какая-то, он сначала очищает переменную, а затем решает ее запомнить в сессии, либо он чего то не додумал либо же в просцессе обворовывания чужих сайтов, часть этой статьи была утрачена.
 :::::  kisin пишет 12.09.2004 @ 23:10 
А как сделать, чтоб скрипт обращался к паролям в бд (другой файл), а не хранить пароли прямо в нём?
if(($user_name=="cleo")&&($user_pass=="password")){

 :::::  n3o пишет 01.11.2004 @ 14:36 
Просто... при проверки пароля обращяйся к файлу где у тебя база паролей, выковыревай из него пароль принадлежащий данному юзеру и проверяй на правильность!
 :::::  evs пишет 05.11.2004 @ 04:22 
Молодец то Алексей молодец... Молодец, что статью из книжечки переписал... "РНР: Настольная Книга Программиста." Авторы: Александр Мазуркевич и Дмитрий Еловой. А вообще это интересная книжечка. Кто начинает учить РНР советую почитать...
 :::::  ScableR пишет 20.12.2004 @ 11:23 
2evs:
> Молодец, что статью из книжечки переписал... "РНР: Настольная Книга Программиста."
> Авторы: Александр Мазуркевич и Дмитрий Еловой.
Прежде чем обвинять автора в плагиате следовало вам, уважаемый, взглянуть на корешок книжеци и сравнить дату её выхода с публикацией данной статьи. К сожалению книжицу у меня самого нету, но по данным инет магазинов её дата выхода - 2004, в то время как статья в 2002 году была опубликована.
IMHO - и кто у кого переписыва? :-)
 :::::  Jet пишет 13.01.2005 @ 15:16 
Коротко, и по сути... Я, например, прочитал и идею понял, а вот реализация - это уж мои проблемы.
А register_globals = On - это только для ленивых (если $_GET $_POST $_SESSION писать неохота), и, собственно, на вкус и цвет так им и надо =).
 :::::  Anonymous пишет 28.03.2009 @ 14:45 
Хешируйте пароль с использованием времени по серверу. Каждый раз пароль будет разным.
Имя:
Email:
URL

Введите сумму двух чисел девять и одинн (девять+одинн=?)
Запомнить мою информацию

* Html запрещен* Ваш E-mail опубликован не будет.

Copyright © 2000-2001 WebScript.Ru nas@webscript.ru
Design © 2001 by Parallax Design Studio (aka Spectator.ru)
Все торговые марки и авторские права на эту страницу принадлежат их соответствующим владельцам.
Сгенерировано за: 0.0297379