
Доказательство концепции для CVE-2026-9256, переполнение буфера в куче в ngx_http_rewrite_module NGINX. Демонстрирует крах рабочего процесса и отказ в обслуживании через специально созданный URI с перекрывающимися группами захвата PCRE. Включает многоэтапную проверку и зондирование keep-alive.
Область применения: только для локальных полигонов, авторизованных сред воспроизведения, проверки уязвимостей и анализа защитных правил. Не используйте на неавторизованных целях. Идеи PoC в этом документе проверяют только удаленно наблюдаемое поведение аварийного завершения worker NGINX, не включают RCE, обход ASLR или стабильную цепочку эксплуатации.
CVE-2026-9256 — это уязвимость переполнения кучи в модуле ngx_http_rewrite_module NGINX. Триггер уязвимости — не просто обращение по определенному фиксированному URI, а зависимость от конкретного шаблона конфигурации rewrite: в rewrite-регулярном выражении существуют перекрывающиеся группы захвата PCRE, а в части replacement (замены) используются несколько переменных захвата, например $1, $2.
Когда злоумышленник создает специальный URI, который приводит к выполнению логики rewrite по соответствующему пути, NGINX при обработке захваченного контента, склейке результатов rewrite или экранировании URI/параметров может столкнуться с несоответствием рассчитанной длины и реальной записи, что в итоге приводит к повреждению памяти кучи worker-процесса.
Поэтому ключ к этой уязвимости — не сам путь /api, а наличие в конфигурации целевого NGINX уязвимого правила rewrite, которое может быть активировано запросом. Путь /api, используемый в PoC по умолчанию, — это всего лишь пример пути в текущей среде воспроизведения; при реальном тестировании путь запроса необходимо корректировать в соответствии с правилами rewrite, содержащими перекрывающиеся группы захвата и ссылающимися на несколько переменных захвата.
Текущая цель PoC — проверка поведения аварийного завершения worker / отказа в обслуживании. Он не пытается создать точную раскладку кучи, не пытается перезаписать адрес возврата или указатели функций, не доказывает удаленное выполнение кода. Стабильные свидетельства, наблюдаемые на удаленной стороне, в основном: аномальное разрывы соединения при отправке триггерного запроса, затем восстановление работы службы NGINX, прерывание keep-alive соединений сбойным worker после триггера.
После срабатывания этой уязвимости не обязательно проявляется фиксированный код 500, 502 или 400. Это связано с тем, что модель master-worker NGINX: после аварийного завершения worker-процесса мастер запускает новый worker. Удаленно атакующий обычно наблюдает не полную недоступность всего сервиса, а внезапный разрыв соединения, тайм-аут чтения, сброс соединения, после чего повторный запрос к / снова дает нормальный ответ.
Поэтому PoC не может судить о наличии уязвимости только по коду состояния HTTP одного запроса. Если отправить один длинный URI, увидеть разрыв соединения и сразу сделать вывод «уязвимость есть», вероятность ложного срабатывания высока. Разрыв соединения может быть также вызван сетевыми колебаниями, тайм-аутом прокси, перехватом промежуточным устройством, ограничением на бэкенде или активным закрытием соединения сервером.
Следовательно, PoC должен быть спроектирован как многоэтапная проверка:
Только когда одновременно наблюдаются «аномальный разрыв соединения при триггере + последующее восстановление службы + многократные разрывы keep-alive», можно с большей уверенностью судить о наличии поведения CVE-2026-9256 (аварийное завершение worker).
Текущий основной путь триггера PoC:
GET /api/++++++++++++++++++++++++++++++++... HTTP/1.1
Host: 127.0.0.1:19321
Здесь /api/ — примерный маршрут в текущем полигоне для попадания в правило rewrite, после него добавляется большое количество символов +. По умолчанию их 4096.
Выбор + обусловлен тремя основными причинами.
Во-первых, + — допустимый символ URI. При отправке обычным HTTP-клиентом он обычно не обрезается и не принудительно перекодируется, как пробел, # и т.д. Поэтому текущий PoC не требует использования raw-сокетов для создания недопустимой строки запроса, как в некоторых обходах request-target.
Во-вторых, большое количество повторяющихся символов позволяет группам захвата rewrite получить достаточно длинный ввод, расширяя масштаб вывода при последующей склейке replacement или экранировании, что упрощает проявление несоответствия между рассчитанной длиной и реальной записью.
В-третьих, структура полезной нагрузки из повторяющихся + проста, что облегчает наблюдение в трафике, журналах и правилах IDS, а также удобно для регулировки длины и тестирования порогов.
Однако следует отметить: + — не единственный теоретически возможный символ-триггер. Реальное условие срабатывания по-прежнему состоит в «попадании в уязвимую конфигурацию rewrite + ввод обрабатывается соответствующими группами захвата + обработка вывода rewrite вызывает переполнение кучи». В разных средах могут потребоваться корректировки маршрута, типа символа и порога длины.
Текущий PoC по умолчанию выбирает символ + в большом количестве. Это не значит, что только + может вызвать проблему. + — наиболее подходящий символ для написания универсального PoC, так как он относительно стабилен в URI, легко отправляется обычными HTTP-клиентами, а его характеристики в захвате трафика четкие.
С точки зрения принципа уязвимости: если символ в процессе обработки rewrite NGINX попадает в логику экранирования NGX_ESCAPE_ARGS и при этом расширяется с исходного 1 байта до 3 байт в виде %XX, это может вызвать несоответствие «рассчитанная длина < реальная запись». То есть триггерная точка в основе своей — не сам +, а «плотное появление символов, подлежащих экранированию в режиме args».
Помимо +, теоретически стоит учитывать следующие символы:
пробел: 0x20
#: 0x23
%: 0x25
&: 0x26
?: 0x3F
управляющие символы: 0x00-0x1F
старшие байты: 0x7F-0xFF
Если эти символы попадут в соответствующий захват и будут обрабатываться как содержимое args при экранировании в replacement rewrite, они вызовут аналогичный эффект расширения. Например:
+ -> %2B
& -> %26
% -> %25
# -> %23
? -> %3F
пробел -> %20
Каждый такой символ теоретически расширяется с 1 до 3 байт, увеличивая длину реальной записи на 2 байта. Если во вводе содержится много таких символов, реальная длина записи может значительно превысить ошибочно рассчитанную длину буфера, что упрощает вызов heap buffer overflow.
Однако пригодность разных символов в PoC неодинакова.
+ — самый стабильный. Он обычно может быть напрямую включен в HTTP request-target, редко обрезается браузером или инструментами командной строки, и не меняет структуру path/query URI. Поэтому текущий PoC использует 4096 символов + в качестве полезной нагрузки по умолчанию.
& также может быть кандидатом, так как в режиме args он экранируется в %26. Но в shell & означает выполнение в фоне, а в URL часто используется как разделитель параметров запроса, поэтому при тестировании нужно обращать внимание на кавычки и положение, иначе запрос может быть отправлен не так, как планировалось.
% также может быть кандидатом, так как он экранируется в %25. Однако % сам по себе является префиксом URL-кодирования, и некоторые клиенты, прокси или фреймворки могут попытаться интерпретировать последовательности %XX. При неправильном составлении цель может получить не исходный символ %, а предварительно обработанный клиентом контент.