Содержание статьи
Впервые аномальное поведение было обнаружено Андреем @d90pwn Данау во время квалификации Real World CTF 2019. Сервер странно реагировал на отправленный в URL символ перевода строки (%0a
). Этой идеей заинтересовались Омар @beched Ганиев и Эмиль @neex Лернер. Эмиль разобрался, почему так происходит, нашел способ эксплуатации и написал рабочий эксплоит, а Омар довел этот баг до получения RCE.
Суть проблемы сводится к тому, что в некоторых конфигурациях FPM злоумышленник может выполнить атаку типа buffer underflow и осуществить запись в адресное пространство, зарезервированное для данных протокола FastCGI. Это позволит выполнять произвольные команды на целевой системе.
Уязвимость получила идентификатор CVE-2019-11043 и провокационное название PHuiP-FPizdaM. Для эксплуатации атакующему не нужно никаких прав, поэтому баг имеет критический статус. Проблема присутствует в обеих ветках PHP — 5 и 7, однако из-за особенностей оптимизации эксплуатация возможна только в PHP седьмой версии.
INFO
Полный список уязвимых версий:
- PHP ветки 7.1.x — все версии ниже 7.1.33;
- PHP ветки 7.2.x — все версии ниже 7.2.24;
- PHP ветки 7.3.x — все версии ниже 7.3.11.
Стенд
Для начала нам нужен стенд. Я буду использовать уже полюбившиеся контейнеры Docker. Если не хочешь возиться с отладкой, то можно просто запустить готовое окружение с vulhub.
docker-compose.yml
version: '2'
services:
nginx:
image: nginx:1
volumes:
- ./www:/usr/share/nginx/html
- ./default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
ports:
- "8080:80"
php:
image: php:7.2.10-fpm
volumes:
- ./www:/var/www/html
Запускается простой командой docker-compose up -d
.
Я собираюсь посмотреть на уязвимость поближе, поэтому будем собирать PHP из исходников. Для начала запускаем Debian.
$ docker run --rm -ti --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --name=phprce --hostname=phprce -p80:80 debian /bin/bash
$ apt update
Теперь позаботимся об установке нужных пакетов.
$ apt install -y build-essential git autoconf automake libtool re2c bison libxml2-dev libgd-dev curl gdb libssl-dev nginx vim nano
Возьмем последнюю уязвимую версию PHP — 7.3.10.
$ cd ~
$ git clone --depth=1 --branch PHP-7.3.10 https://github.com/php/php-src.git
$ cd php-src
Сконфигурируем PHP с поддержкой PHP-FPM.
$ ./buildconf --force
$ ./configure --enable-debug --enable-fpm --with-openssl --with-fpm-user="www-data" --with-fpm-group="www-data"
Теперь дело за компиляцией и установкой. Здесь все стандартно.
$ make
$ make install
Меняем имена стандартных конфигурационных файлов.
$ mv /usr/local/etc/php-fpm.conf.default /usr/local/etc/php-fpm.conf
$ mv /usr/local/etc/php-fpm.d/www.conf.default /usr/local/etc/php-fpm.d/www.conf
Далее изменяем путь до папки, где находятся конфиги.
$ sed -s -i 's/=NONE/=\/usr\/local/' /usr/local/etc/php-fpm.conf
В конфиге PHP-FPM (/usr/local/etc/php-fpm.d/www.conf
) настраиваем количество дочерних процессов. Чтобы было проще отлаживать, рекомендую поставить 1.
pm = static
pm.max_children = 1
Теперь дело за файлами конфигурации для nginx.
/etc/nginx/sites-enabled/default
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.php;
server_name _;
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}
}
В fastcgi_pass
указан адрес нашего PHP-FPM, по умолчанию он висит на 9000-м порте. Некоторые части конфига я поясню, когда буду разбирать уязвимость.
Затем нужно создать файл PHP в корне веб-сервера (/var/www/html/
). Тут подойдет даже пустой скрипт, главное, чтобы он имел расширение .php и nginx отправлял его к PHP. Я создал index.php
, который выводит приветствие.
/var/www/html/index.php
<?php
echo 'Hi there!';
Теперь все готово, можно запускать nginx.
$ service nginx start
А затем и PHP-FPM через отладчик.
$ gdb --args php-fpm --nodaemonize
Для GDB включаем возможность отлаживать дочерние процессы и запускаем сервис.
set follow-fork-mode child
r
Детали уязвимости
Первым делом заглянем в коммит, который патчит уязвимость.
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1151: path_info = env_path_info ? env_path_info + pilen - slen : NULL;
1152: tflag = (orig_path_info != path_info);
/php-src-php-7.3.11/sapi/fpm/fpm/fpm_main.c
1151: path_info = (env_path_info && pilen > slen) ? env_path_info + pilen - slen : NULL;
1152: tflag = path_info && (orig_path_info != path_info);
Как видишь, добавлены дополнительные проверки для переменных path_info
и tflag
. В этой части кода обрабатываются пути вида /info.php/test
.
Поставим брейк-пойнт чуть выше запатченных строк, на строке 1143, и попробуем отправить GET-запрос с байтом переноса строки (%0a
).
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»