30.4. Общее управление сессиями

Поведение сессий, принятое по умолчанию, можно изменить, используя статические методы класса Zend_Session. Все управление производится посредством Zend_Session, включая конфигурирование через опции, предоставляемые расширением ext/session, с использованием метода Zend_Session::setOptions().

30.4.1. Zend_Session::setOptions()

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

Для того, чтобы передать опции, просто передайте базовое имя (без приставки session.) как часть массива методу Zend_Session::setOptions(). Если опции не были установлены, то Zend_Session будет сначала использовать рекомендуемые опции, затем установки по умолчанию из php.ini. Предложения по оптимизации работы с опциями отправляйте в список рассылки fw-auth@lists.zend.com.

"Автоматическое" конфигурипрование этой компоненты с использованием Zend_Config_Ini:

Пример 30.17. Использование Zend_Config для конфирурирования Zend_Session

<?php
$config = new Zend_Config_Ini('myapp.ini', 'sandbox');
require_once 'Zend/Session.php';
Zend_Session::setOptions($config->toArray()); 
?>

Используемый файл "myapp.ini":

Пример 30.18. myapp.ini


;  Настройки по умолчанию для производственного сервера
[live]
; bug_compat_42
; bug_compat_warn
; cache_expire
; cache_limiter
; cookie_domain
; cookie_lifetime
; cookie_path
; cookie_secure
; entropy_file
; entropy_length
; gc_divisor
; gc_maxlifetime
; gc_probability
; hash_bits_per_character
; hash_function
; имя должно быть уникальным для всех приложений, использующих
; одно и то же доменное имя
name = UNIQUE_NAME
; referer_check
; save_handler
; save_path
; serialize_handler
; use_cookies
; use_only_cookies
; use_trans_sid

; remember_me_seconds = <integer seconds>
; strict = on|off

; Наш тестовый сервер использует те же настройки, что и наш
; производственный сервер за исклочением тех, что переопределяются ниже:
[sandbox : live]
; Не забудьте создать эту директорию и сделать ее доступной для чтения
; и записи через PHP
save_path = /home/myaccount/zend_sessions/myapp
use_only_cookies = on
; Когда индентификатор сессии сохраняется в куках, запрашивать TTL
; через 10 дней
remember_me_seconds = 864000

30.4.2. Опции

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

  • boolean strict - отключает автоматический запуск Zend_Session при использовании new Zend_Session_Namespace().

  • integer remember_me_seconds - время хранения идентификатора сессии в куках после того, как агент пользователя завершит свою работу (т.е. когда окно броузера будет закрыто).

  • string save_path - корректное значение зависит от системы и должно указываться разработчиком с использованием абсолютного пути к директории, доступной для чтения и записи процессом PHP. Если директория на заданном пути не существует или недоступна, то Zend_Session бросает исключение во время старта (т.е. когда вызывается метод start())

    [Замечание] Угроза безопасности

    Если путь доступен для чтения другими приложениями, то возможен угон сессий (session hijacking). Если путь доступен для записи другими приложениями, то возможно заражение сессий (session poisoning). Если этот путь используется совместно с другими пользователями или с другими PHP-приложениями, то это создает различные угрозы безопасности, включая кражу содержимого сессий, угон сессий и коллизии при "сборке мусора" (например, работа приложения другого пользователя может вызвать удаление файлов сессий вашего приложения).

    Например, атакующий может зайти на сайт жертвы для получения сессионных куков. Затем он изменяет путь куков на собственный домен для того же сервера и заходит на собственный сайт для выполнения var_dump($_SESSION). Вооруженный информацией о том, как жертва использует данные в своих сессиях, атакующий может модифицировать данные сессии (заражение сессии), возвращает значение пути куков на исходный (ведущий на сайт жертвы) и делает запросы с сайта жертвы, используя зараженную сессию. Даже если оба приложения на том же сервере не имеют прав чтения/записи в директории save_path другого приложения, то в том случае, если можно заходить в директорию save_path и атакующий имеет контроль над одним из сайтов, он может изменить save_path своего сайта на save_path жертвы и таким образом выполнить заражение сессии под некоторыми общими конфигурациями PHP. Поэтому значение save_path не должно быть достоянием общественности и должно быть изменено на секретное значение, уникальное для каждого приложения.

  • string name - корректное значение зависит от системы и должно устанавливаться разработчиком, с использованием короткого значения, уникального для приложения ZF.

    [Замечание] Угроза безопасности

    Если настройки в php.ini для session.name одинаковые для приложений (например, "PHPSESSID" для настроек по умолчанию) и через одно доменное имя доступны два и более приложений (например, http://www.somewebhost.com/~youraccount/index.php), то они будут использовать одни и те же данные сессий для посетителей, посещающих оба сайта. Это может привести к разрушению данных сессий.

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

    [Замечание] Угроза безопасности

    Если эта опция не включена, то аттакующий может легко "фиксировать" идентификаторы сессии, используя ссылки на атакуемый сайт вида http://www.victim-website.com/index.php?PHPSESSID=fixed_session_id. Фиксация будет работать, если жертва не всегда имеет куки с идентификатором сессии для victim-website.com. Как только жертва будет использовать известный атакующему идентификатор сессии, атакующий может попытаться угнать сессию, имитируя реального пользователя и эмулируя агента жертвы.

30.4.3. regenerateId()

30.4.3.1. Введение: идентификаторы сессий

Введение: Наилучшей практикой в использовании сессий с ZF будет использование куков вместо сохранения идентификатора сессии в URL для отслеживания отдельных пользователей. По умолчанию эта компонента использует только куки для хранения идентификатора сессии. Значением, сохраняемым в куках, является уникальный идентификатор сессии. Расширение ext/session использует этот идентификатор для поддержки однозначно определяемой связи "один-к-одному" между посетителем сайта и хранилищем постоянных данных сессии, уникальным для каждого посетителя. Zend_Session* является оберткой к этому механизму хранения ($_SESSION) с объектно-ориентированным интерфейсом. К сожалению, если атакующий получил доступ к идентификатору сессии в куках, то он может угнать сессию посетителя. Эта проблема не является присущей только PHP или Zend Framework. Метод regenerateId() позволяет приложению изменять идентификатор сессии (сохраненный в куках посетителя) на новое случайное значение. Замечание: Несморя на то, что эти термины не равнозначны, для удобочитаемости мы будем попеременно использовать "агент пользователя" и "веб-броузер".

Почему?: Если атакующий получил валидный идентификатор сессии, то он может имитировать реального пользователя (жертву) и затем получить доступ к конфиденциальной информации или манипулировать данными жертвы через ваше приложение. Изменение идентификатора сессии помогает защитить пользователя от угона сессии. Если идентификатор сессии был изменен и атакующий не знает его новое значение, то он не может использовать новый идентификатор сессии в своей попытке угона сессии посетителя. Даже если атакующий получил доступ к старому идентификатору сессии, то regenerateId() перемещает данные сессии со старого идентификатора на новый, и поэтому данные этой сессии не будут доступны через старый идентификатор.

Когда использовать regenerateId(): Добавление Zend_Session::regenerateId() в файл загрузки Zend Framework является одним из самых безопасных и надежных способов регенерации идентификаторов сессии в куках агента пользователя. Само по себе отсутствие условной логики, определяющей, когда регенерировать идентификатор сессии, не является признаком плохо разработанного кода. Но, несмотря на то, что регенерация при каждом запросе пересекает некоторые возможные пути атак, не все хотят мириться с небольшой потерей в производительности и пропускной способности, связанными с регенерацией. Поэтому приложения обычно пытаются определить ситуации, связанные с наибольшим риском, и только тогда регенерируют идентификаторы сессий. В случаях, когда привилегии сессии посетителя сайта "обостряются" (например, посетитель заново проходит аутентификацию до изменения его личного "профиля") или когда производятся "чувствительные" для безопасности изменения параметров сессии, используется regenerateId() для регенерации идентификатора сессии. Если вы вызываете функцию rememberMe(), то не используйте regenerateId(), т.к. первая функция вызывает вторую. Если пользователь успешно залогинился на вашем сайте, используйте rememberMe() вместо regenerateId().

30.4.3.2. Угон и фиксация сессии

Отсутствие XSS-уязвимостей на сайте помогает предотвратить угон сессий c него. Согласно статистике Secunia, XSS (межсайтовый скриптинг) - довольно распространенное явление. Лучше минимизировать возможный ущерб от XSS, следуя наилучшей практике программирования, чем предполагать, что этого никогда не случится с вами. Атакующему, использующему XSS, не нужно иметь прямой доступ к сетевому трафику жертвы. Если жертва уже имеет сессионные куки, то XSS с внедрением кода Javascript позволит атакующему прочитать куки и украсть сессию. Если жертва не имеет сессионные куки, то, используя XSS с внедрением кода Javascript, атакующий может создать куку с заранее известным идентификатором сессии в броузере жертвы, затем установить идентичную куку в своей системе, чтобы угнать сессию жертвы. Если жертва посетит сайт атакующего, то атакующий может также сэмулировать и другие доступные для идентификации характеристики агента пользователя жертвы. Если ваш сайт имеет XSS-уязвимости, то атакующий может внедрить AJAX-код, который скрытно "заходит" на сайт атакующего, и атакующий может узнать характеристики броузера жертвы и о скомпрометированной сессии на сайте жертвы. Но несмотря на все это, атакующий не может изменить данные сессии на стороне сервера при условии, что разработчик корректно установил значение опции save_path.

Сам по себе вызов Zend_Session::regenerateId() в то время, как сессия еще только начинает использоваться, не предотвращает атаку через фиксацию сессии, за исключением того случая, когда вы можете отличить сессию, созданную атакующим, имитирующим личность жертвы. На первый взгляд это противоречит предыдущему утверждению, но до тех пор, пока мы не будем считать атакующим того, кто первый иницировал создание настоящей сессии на вашем сайте. Сессия сначала используется атакующим, который знает результат инициализации (regenerateId()). Атакующий затем использует новый идентификатор сессии вместе с найденной XSS-уязвимостью или добавляет идентификатор сессии в ссылку на сайт атакующего (работает в том случае, если use_only_cookies = off).

Если вы можете различать атакующего и жертву, использующих один и тот же идентификатор сессии, то это может решить проблему увода сессии. Тем не менее, такое распознавание обычно принуждает к поиску компромисса с юзабилити, т.к. методы различения нередко являются неточными. Для примера, если запрос получен с IP в стране, отличающейся от IP запроса, при котором была создана сессия, то это может означать, что новый запрос производится уже атакующим. При выполнении следующих условий приложение сайта не сможет различить жертву и атакующего:

  • - атакующий первый иницировал сессию на вашем сайте для получения валидного идентификатора сессии

  • - атакующий использует XSS-уязвимость на вашем сайте для создания куки в броузере жертвы с валидным идентификатором сессии (т.е. фиксация сессии)

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

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

Пример 30.19. Анонимная сессия и фиксация сессии

<?php
require_once 'Zend/Session.php';
$defaultNamespace = new Zend_Session_Namespace();
 
if (!isset($defaultNamespace->initialized))
{ 
    Zend_Session::regenerateId(); 
    $defaultNamespace->initialized = true;
} 
?>

30.4.4. rememberMe(integer $seconds)

Обычно сессия заканчивается, когда агент пользователя завершает сеанс работы - например, пользователь закрыл окно броузера. Тем не менее, после того как пользователь зашел в систему, может понадобиться хранить его сессию 24 часа и больше. Программное обеспечение форумов обычно предоставляет пользователю возможность выбирать, сколько времени должна храниться сессиия. Используйте Zend_Session::rememberMe() для отправки обновленной сессионной куки агенту пользователя со временем жизни, по умолчанию равному remember_me_seconds, который равен 2 неделям до тех пор, пока вы не измените это значение через метод Zend_Session::setOptions(). Для того, чтобы помешать угону или фиксации сессии, используйте эту функцию, когда пользователь успешно прошел аутентификацию и ваше приложение выполнило "регистрацию"

30.4.5. forgetMe()

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

30.4.6. sessionExists()

Используйте этот метод для определения того, есть ли уже сессия для текущего агента пользователя/запроса. Он может использоваться до старта сессии и независимо от всех других методов Zend_Session и Zend_Session_Namespace.

30.4.7. destroy(bool $remove_cookie = true, bool $readonly = true)

Zend_Session::destroy() уничтожает все постоянные данные, связанные с текущей сессией. Это не влияет на переменные в PHP, поэтому ваши сессии с пространствами имен (экземпляры Zend_Session_Namespace) остаются доступными для чтения. Для выхода из системы установите необязательный параметр в true (по умолчанию он равен true), при этом будет удалена кука с идентификатором сессии в агенте пользователя. Установленный в true необязательный параметр $readonly блокирует возможность записи в данные сессии (т.е. в $_SESSION) для экземпляров Zend_Session_Namespace и методов Zend_Session.

[Замечание] Исключения

По умолчанию $readonly установлен в true и дальнейшие действия, подразумевающие запись в хранилище данных сессии, вызовут генерацию исключения.

30.4.8. stop()

Этот метод не делает ничего, кроме переключения флага в Zend_Session для предотвращения дальнейшей записи в хранилище данных сессии (т.е.$_SESSION). Одним из вариантов его использования является временное отключение возможности записи в хранилище данных сессии через экземпляры Zend_Session_Namespace или методы Zend_Session во время выполнения кода, связанного с отображением вида. Попытка выполнить действия, подразумевающие запись через эти экземпляры или методы вызовет генерацию исключения.

30.4.9. writeClose($readonly = true)

Закрывает сессию, завершает запись и отсоединяет $_SESSION от средства хранения на сервере. Это завершит внутреннее преобразование данных для данного запроса. Необязательный параметр булевого типа $readonly позволяет отключить возможность записи (т.е. генерация исключения при попытке записи через любые методы Zend_Session_Namespace или Zend_Session).

[Замечание] Исключения

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

30.4.10. expireSessionCookie()

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

30.4.11. setSaveHandler(Zend_Session_SaveHandler_Interface $interface)

Большинство разработчиков находят достаточным использовать принятый по умолчанию механизм хранения сессионных данных. Этот метод предоставляет объектно-ориентированную обертку для session_set_save_handler() .

30.4.12. namespaceIsset($namespace)

Используйте этот метод для определения того, существует ли пространство имен с данным именем или определенный индекс в данном пространстве имен.

[Замечание] Исключения

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

30.4.13. namespaceUnset($namespace)

Вместо создания экземпляра Zend_Session_Namespace для пространства имен и итерации по его содержимому для удаления каждой отдельной записи используйте метод namespaceUnset($namespace) для быстрого удаления всего пространства имен и его содержимого. Как это справедливо для всех массивов в PHP, если переменная, содержащая массив, уничтожена, и этот массив содержал другие объекты, то эти объекты не уничтожаются, если они были сохранены по ссылке в других массивах/объектах. Это означает, что они остаются доступными через другие переменные. Поэтому namespaceUnset() не производит "глубокое" удаление содержимого записей в пространстве имен. За более подробной информацией обращайтесь к разделу References Explained в документации по PHP.

[Замечание] Исключения

Если пространство имен недоступно для записи (например, после destroy()), то будет сгенерировано исключение.

30.4.14. namespaceGet($namespace)

Не рекомендуется к использованию: Используйте getIterator() в Zend_Session_Namespace. Этот метод возвращает массив содержимого пространства имен $namespace. Этот метод позднее может быть определен как закрытый. Если вы считаете, что есть разумные причины оставить этот метод открытым, то пишите в список рассылки fw-auth@lists.zend.com. Участие в связанных темах также приветствуется :)

[Замечание] Исключения

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

30.4.15. getIterator()

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

Пример 30.20. Уничтожение всех пространств имен

<?php
foreach(Zend_Session::getIterator() as $space) {
    try {
        $core->namespaceUnset($space);
    } catch (Zend_Session_Exception $e) {
        return; // possible if Zend_Session::stop() has been executed
    }
}

?>
[Замечание] Исключения

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