Уязвимость в PHP, о которой пойдет речь в данной статье, была обнаружена в декабре 2008 пользователем форума sla.ckers.org под ником barbarianbob. В одном из своих ответов он предложил новый способ реализации локального инклуда, когда атакующий имеет возможность указывать произвольное имя файла, но не способен отбросить расширение при включенной директиве PHP magic_quotes_gpc в виду того факта, что широко используемый в таких целях NULL-byte (0x00) подвергается экранированию и, как следствие, становится неэффективным. Значение подобного способа невозможно переоценить, так как на данный момент почти все LFI-векторы основаны на использовании NULL-байта и, как правило, имеют данное ограничение. До настоящего времени информация оставалась незамеченной, но недавно итальянская команда USH опубликовала уникальный материал, расширяющий понимание новой уязвимости и дополняющий представление о ее причинах и других способах реализации.
Уязвимость основана на двух особенностях в функциях PHP для взаимодействия с файловой системой:
- Нормализация пути
PHP обрабатывает строку, содержащую путь до файла или папки, особым образом, в частности лишние символы “/” и “/.” удаляются. - Усечение пути
PHP в зависимости от платформы имеет ограничение на длину пути, определяемой константой MAX_PATH, в результате чего все символы, находящиеся за пределами этого значения, отбрасываются.
Нормализация пути
Всем известно, что в PHP имеется большое количество возможностей для быстрого и простого программирования. PHP способен совершать множество необходимых действий самостоятельно, не обременяя разработчика лишними заботами. Такая направленность PHP однако имеет и обратную сторону – возможно именно по причине излишней обработки путей возможна новая уязвимость.
Примеры нормализации пути:
<?php include('/etc//passwd'); //работает include('/etc///passwd'); //работает include('/etc/./passwd'); //работает include('/etc/passwd/.'); //работает include('/etc/passwd/./.'); //работает ?>
Нормализация пути может быть использована атакующим для обхода проверки расширения или имени файла в различных ситауциях, например при загрузке файлов или в веб-приложении, реализующем преднамеренный просмотр своего исходного кода. Уязвимость имеет место при неправильно реализованной проверке, которая основывается на принципе черного списка (blacklist), т.е. веб-приложение сверяет данные со списком нежелательных значений. Например:
<?php $parts = pathinfo($_GET['filename']); $ext = $parts['extension']; $blacklist = array('php', 'phtml', 'php3', 'php4', 'php5'); if(in_array($ext, $blacklist)) { die('Invalid input'); } ?>
Если в filename передать значение “file.php”, то проверка сработает успешно, но если же добавить к этому значению “/.“, то pathinfo() вернет неверный массив, в котором элемент “dirname” будет равен “file.php”.
Причина уязвимости кроется в функции virtual_file_ex() (TSRM/tsrm_virtual_cwd.c):
tok=NULL; ptr = tsrm_strtok_r(path_copy, TOKENIZER_STRING, &tok); while (ptr) { ptr_length = strlen(ptr); /* */ if (IS_DIRECTORY_UP(ptr, ptr_length)) { /* */ } else if (!IS_DIRECTORY_CURRENT(ptr, ptr_length)) { /* */ } ptr = tsrm_strtok_r(NULL, TOKENIZER_STRING, &tok); }
Путь разбивается на части символом, значение которого хранится в TOKENIZER_STRING:
#define TOKENIZER_STRING "/\\" // Win32 #define TOKENIZER_STRING "/\\" // NETWARE #define TOKENIZER_STRING "/" // other (*nix)
Затем в цикле каждая часть проверяется на соответствие с IS_DIRECTORY_UP и IS_DIRECTORY_CURRENT, значения которых определены так:
#define IS_DIRECTORY_UP(element, len) \ (len >= 2 && !php_check_dots(element, len)) #define IS_DIRECTORY_CURRENT(element, len) \ (len == 1 && element[0] == '.')
Таким образом, каждая часть пути, разделенная на TOKENIZER_STRING, анализируется в цикле с помощью нехитрых проверок, при чем ни одна из них не вызывает ошибку, что в конечном счете приводит к уязвимости, так как все нейтральные “./” убираются.
Стоит отметить, что простая последовательность из символов “/”, стоящая в конце пути, не приведет к желаемому результату, если используется “чистый” PHP без всяких патчей. Однако PHP с Suhosin patch будет принимать такие значения:
<?php include('/etc/passwd///'); ?>
Все дело в том, что Suhosin patch заменяет функцию realpath(), отсюда возникают расхождения. Патч полностью убирает замыкающие символы “/”, в то время как стандартный PHP преобразует все дополнительные символы “/” в один, что сделает имя файла неправильным. Тем не менее, одиночные символы “/.” или их последовательность будут работать на любой установке PHP.
Усечение пути
Вторая особенность, позволяющая говорить о новой уязвимости в PHP, – это автоматическое усечение длины пути подобно тому, как это происходит в MySQL с ограниченными по длине колонками. Благодаря этому, в локальных инклудах NULL-байт может быть заменен на последовательность символов, состоящих из “/”, “./”, “/.” и других. Например имеется следующий уязвимый скрипт:
<?php // magic_quotes_gpc=on include('includes/'.$_GET['act'].'.php'); ?>
Это классическая LFI-уязвимость (local file include). В данном примере атакующий может изменять центральную часть аргумента функции include(), поэтому проведение RFI (remote file include) является невозможным, так как для этого необходимо контролировать начало значения для указания протокола передачи данных (http, ftp, data, etc). Для реализации локального инклуда атакующий может внедрить произвольный PHP-код в файловую систему сервера для дальнейшего его выполнения через LFI. При этом очень часто требуется отбросить расширение файла, что обычно достигается путем использования NULL-байта. Как известно, многие функции PHP, в частности ereg(i)_*, include, require, etc не являются бинарно безопасными, т.е. как и в C/C++, NULL-байт будет интерпретироваться как конец строки. Использование NULL-байта затрудняется тем, что включенная директива magic_quotes_gpc, а также применение функции addslashes() экранируют нулевой символ, что существенно сокращает поле его применения.
Пример LFI-вектора с NULL-байтом:
index.php?act=../../../../../etc/passwd%00
После экранирования в include() попадет значение “includes/../../../../../etc/passwd \0.php”.
Новый вектор способен предоставить полноценную альтернативу NULL-байту:
index.php?act=../../../../../etc/passwd/////[…]/////
Количество символов “/” отличается на разных платформах, но в большинстве случаев максимальная длина полного пути (т.е. после преобразования относительного в абсолютный путь) равна 4096 байт. Само усечение пути выполняется в main/streams/plain_wrapper.c и main/fopen_wrappers.c неправильным вызовом snprintf():
snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename);
Как и в MySQL, PHP без всяких вопросов и предупреждений обрезает строку, длина которой становится равной MAXPATHLEN. Значение MAXPATHLEN определяется так:
/main/php.h:
#ifndef MAXPATHLEN # ifdef PATH_MAX # define MAXPATHLEN PATH_MAX # elif defined(MAX_PATH) # define MAXPATHLEN MAX_PATH # else # define MAXPATHLEN 256 # endif #endif
/win32/param.h:
#ifndef MAXPATHLEN # define MAXPATHLEN _MAX_PATH #endif
Усечение пути в сочетании с нормализацией пути позволяет заменить NULL-байт для установок PHP с включенным magic_quotes_gpc. Последовательность после желаемого пути может состоять из символов “/”, “/.”, “./”; в win32 этот список гораздо шире: 0x20 (пробел), 0x22 (“), 0x2E (.), 0x3c (<), 0x3e (>), 0x5c (\). Следует заметить, что в стандартных сборках PHP для удачной реализации уязвимости последним символом пути должна быть точка. Что касается символа “/”, то его нормализация происходит на всех платформах, но в стандартной сборке PHP последовательность из слэшей преобразуется в один, поэтому очень часто его применение может быть невозможно. Однако по утверждениям Стефана Эссера использовать слэш в атаках можно на любых *bsd платформах, даже без Suhosin patch. По собственным наблюдениям могу предположить, что на всех *bsd-серверах длина последовательности составляет 1024 байта, в то время как на linux эта цифра в четыре раза больше. В windows была замечена самая короткая последовательность – не более 270 байт.
Угулублясь в исходный код PHP, становится очевиным, что уязвимость обязана своему существованию такой директиве конфигурации PHP как include_path, которая представляет собой аналог PATH на unix-системах. Всякий раз когда вызывается include() или require() с относительным путем в качестве аргумента, PHP производит попытки чтения файлов, последовательно подставляя папки, разделенных символов “:”, из значения директивы. По умолчанию include_path равен “.:”, но обычно в это значение добавляется еще несколько путей до различных библиотек, PEAR и прочего. В ходе исследований команды USH выяснилось, что условием удачной атаки является наличие хотя бы одного абсолютного пути в значении include_path.
Итоги
Особенности PHP при обработке путей позволяют выделить новую уязвимость в популярном языке программирования веб-приложений. С помощью нее становится возможным осуществление обхода запрещенных расширений или имен файлов. Кроме того, нормализация пути вкупе с усечением дают новую альтернативу NULL-байту при проведении локальных инклудов. При этом универсальным вектором атаки для *nix является последовательность из символов “/.”, а для windows – “.”. Условия реализации атаки могут варьироваться в зависимости от конкретного сервера, в частности от платформы и конфигурации PHP. Известными условиями являются:
- в стандартных сборках PHP последним символом пути должна быть точка
- include_path должен содержать абсолютный путь
- вектор атаки для метода GET должен умещаться в пределы максимальной принимаемой веб-сервером длины URL
Для составления векторов рекомендую использовать онлайн-сервис Hackvertor.
Leave a Reply