четверг, 15 апреля 2010 г.

Сессии и register_globals в PHP, или как я пытался получить инвайт на Habrahabr.ru

Недавно решил получить инвайт на Habrahabr.ru. Не то, чтобы я являлся активным писателем, но хотелось бы иной раз иметь возможность черкнуть пару-тройку комментариев к какой-либо заинтересовавшей заметке. В общем, прочитал на их сайте, что любой может заработать инвайт, что называется, своим собственным умом. Всё, что для этого нужно - написать интересную статью и поместить её в тамошнюю "песочницу". Если статья понравится кому-либо из читателей, кто имеет возможность дать инвайт, он его отправит на email, указанный при помещении статьи соискателем. Да, и еще один нюанс - статья висит в "песочнице" ровно одну неделю. То есть, если за неделю вас никто не отметил - ну что ж... вам просто не повезло :)
В общем, 7 апреля сего года выложил я собственноручно написанный материал (статья идет ниже). Но вот вчера - 14 апреля - обнаружил, что и статьи уже в "песочнице" нет, и инвайта в "мыле" :) Видимо, пока не судьба.
Ну а чтобы статья не пропадала даром, решил поместить её в блог. Возможно, кому-то она пригодится.

=====
Сессии и register_globals в PHP

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

Рассмотрим тестовый пример:

 session_start();
 $_SESSION['var'] = 5;
 $var = 2; 
 echo 'Var value = ' . $var . ', ';
 echo 'Session value = ' . $_SESSION['var'];

При первом запуске скрипта мы увидим, что Var value = 2 и Session value = 5. То есть, вроде бы всё в порядке. Обновим страницу в браузере по нажатию F5. И тут мы видим, что уже и Var value, и Session value принимают при выводе значение 2 ! Это же значение формируется скриптом и при дальнейших обновлениях страницы.
Давайте попробуем разобраться, почему так происходит. PHP обеспечивает доступ к глобальным массивам, среди которых нас в данном случае будут интересовать два: $GLOBALS и $_SESSION. Первый, как следует из названия, хранит значения глобальных переменных (переменная $var, объявленная на верхнем уровне, является глобальной), второй - значения конкретной сессии пользователя.
Сразу скажу, что я не знаю точно, как именно внутри 5-ой версии PHP устроено хранение значений переменных, но проведенные мной эксперименты показали, что дальнейшее описание достаточно близко соответствует действительности.
Итак, при первом запуске скрипта и тот, и другой массив пуст. В процессе работы скрипта в массиве $_SESSION создается ссылка 'Var' на контейнер, содержащий значение переменной, равное 5, а в массиве $GLOBALS создается ссылка 'Var' на другой контейнер, имеющий значение 2. То есть, метки переменных имеют одинаковые названия, но указывают они на различные ячейки памяти, если упрощенно. При выполнении скрипта на экране мы видим соответствующие значения.
Теперь самое интересное. При повторном выполнении уже как минимум один раз отработавшего скрипта, в процессе обработки команды session_start() PHP подгружает в массив $_SESSION переменные сессии пользователя, а так как режим register_globals включен (On), в массиве глобальных переменных $GLOBALS автоматически создается переменная с именем, совпадающим с ключем массива $_SESSION (в нашем случае это будет $GLOBALS['var']). Причем контейнер в памяти, на который будет указывать эта ссылка, является тем же самым контейнером, на который указывает ссылка из массива $_SESSION!
Таким образом, далее, когда мы присваиваем глобальной переменной $var значение 2, фактически мы изменяем общую переменную, на которую указывают и $GLOBALS['var'], и $_SESSION['var']. Отсюда получаем, что при выводе и Var value, и Session value принимают одно и то же значение - "2".
Как можно с этим бороться ? Ну, во-первых, можно выключить register_globals, установив его в 'Off' (это, кроме всего прочего, поможет и для обеспечения более высокого уровня безопасности в случае не очень качественно написанных скриптов). Сделать это можно, например, путем редактирования соответствующей строки файла конфигурации PHP - php.ini. Если же доступа к нему нет, можно добавить директиву "php_flag register_globals off" в файл .htaccess, либо указать прямо в начале php-скрипта "ini_set('register_globals',0);".
Следующий способ - не использовать имена глобальных переменных, которые являются ключами в массиве $_SESSION (допустим, мы можем назвать нашу переменную не $var, а, например, $uniq_var).
Ну и, наконец, можно попробовать в самом начале скрипта освободить "ненужные" значения глобальных переменных, которые были установлены PHP автоматически. Для нашего частного случая это можно сделать через вызов "unset($GLOBALS['var']);" сразу после строки, содержащей вызов session_start().
В заключение отмечу, что данное поведение наблюдалось мной в PHP версии 5.2.6, и, возможно, в других версиях оно будет иным. В любом случае, надеюсь, что я смог кому-то помочь. Как говорится, предупрежден - значит, вооружен :)
=====

2 комментария:

  1. Благодарю! Как раз искал описание этой проблемы. Жаль, что нет способоа пофиксить это через php.ini без выключения register_globals...

    ОтветитьУдалить
  2. Рад был помочь (сейчас даже сам перечитал статью, чтобы вспомнить, о чем рам речь шла =) А по поводу register_globals - надо избавляться от необходимости того, чтобы он был включен (ну, лично мое мнение). Просто потенциальная дыра в безопасности.

    ОтветитьУдалить