Hispasec
Security Advisory
http://hispasec.com/

Name         : PHP
Threat level : LOW
Class        : Information Disclosure
Discovered   : 2009-08-07
Published    : ---
Credit       : Martin Noga, Matthew "j00ru" Jurczyk, Gynvael Coldwind
Vulnerable   : PHP 5.2.10 / 5.3.0, tested on Windows XP SP3/Vista SP2 x86
              (though supposed to work on all platforms).



==[ Abstract ]==

PHP is a well-known script programming language, commonly used by the Apache web server,
present on most machines running under Linux/Unix kernel control. It is mostly used to run
scripts on the WWW server side, but it can be also used from the command-line level. Since the
PHP server is running in the context of Apache, an exception generated by PHP will most likely
result in the entire apache process going down. By using the vulnerability describing here, one
is able to crash the PHP server (in its default configuration), both locally and remotely.



==[ Memory Disclosure #1 ]==

The first vulnerability described here allows a potential attacker to steal 4 junk bytes from the
PHP server heap, because of the lack of an appropriate sanity check regarding the JPEG file
being parsed by the internal exif_scan_JPEG_header function (file ext\exif\exif.c).

To be more exact, the unsecure code is listed below:

--- cut ---
case M_SOF0:
(...)
case M_SOF15:
exif_process_SOFn(Data, marker, &sof_info);
ImageInfo->Width  = sof_info.width;
ImageInfo->Height = sof_info.height;
if (sof_info.num_components == 3) {
ImageInfo->IsColor = 1;
} else {
ImageInfo->IsColor = 0;
}
break;

(...)

static void exif_process_SOFn (uchar *Data, int marker, jpeg_sof_info *result)
{
/* 0xFF SOSn SectLen(2) Bits(1) Height(2) Width(2) Channels(1)  3*Channels (1)  */
result->bits_per_sample = Data[2];
result->height          = php_jpg_get16(Data+3);
result->width           = php_jpg_get16(Data+5);
result->num_components  = Data[7];

--- cut ---

As one can see, there is no validation check whether the file size is big enough to contain
the bits_per_sample/height/width/num_components fields present in the code. In particular, the section
size could be set to 0x0002, thus containing only 2 bytes (the length WORD) with no real section data
at all. As the code goes, four structure fields are being filled with some unitialized piece of memory,
two of which are being passed back to the high-level PHP script. The programmers is able to
easily obtain the values by referecing the $result['COMPUTED']['Height'] and $result['COMPUTED']['Width']
objects.

What is more, the above vulnerability could potentially lead to Denial of Service conditions, in case
the memory being accessed by the program would not exist (this could happen if the heap allocation
of size 2 would lie close to a page-boundary. According to the author's research, such a situation
takes place very rarely, thus it is not considered a reasonable DoS attack.


==[ Memory Disclosure #2 ]==

The second vulnerability is based on a very similar bug existing in the code. This time, one byte of the
stack memory can be passed back to the attacker's script due to a wrong assumption made by the source code
author.

The following php_read2 function code presents the essence of the issue (file ext\standard\image.c):

--- cut ---
static unsigned short php_read2(php_stream * stream TSRMLS_DC)
{
unsigned char a[2];

/* just return 0 if we hit the end-of-file */
if((php_stream_read(stream, a, sizeof(a))) <= 0) return 0;

return (((unsigned short)a[0]) << 8) + ((unsigned short)a[1]);
}

(...)

switch (marker) {
case M_SOF0:
(...)
case M_SOF15:
if (result == NULL) {
/* handle SOFn block */
result = (struct gfxinfo *) ecalloc(1, sizeof(struct gfxinfo));
length = php_read2(stream TSRMLS_CC);
result->bits     = php_stream_getc(stream);
result->height   = php_read2(stream TSRMLS_CC);
result->width    = php_read2(stream TSRMLS_CC);
result->channels = php_stream_getc(stream);
--- cut ---


To be more exact, the php_stream_read "if" statement makes sure there are *ANY* bytes
read from the target file, but doesn't check the actual return value. This simply means that
one is able to make the functon read only one byte from the file (leaving a[1] unitialized) and then
return the entire a[] array, thus letting the user access one leaked byte from the server's stack.

What should be noted is that this attack can not be extended to many php_read2 function calls,
since the described conditions are met only in case of the EOF marker being met after sucessfully
reading one byte from the file. Such a scenario may take place only once during the exported function's
calls, because of obvious reasons.


==[ Memory Disclosure #3 ] ==

The last vulnerability makes it possible to read a maximum of two bytes out of the server's heap memory.
The vulnerable piece of code is shown below:

--- cut ---
static void exif_process_APP12(image_info_type *ImageInfo, char *buffer, size_t length TSRMLS_DC)
{
size_t l1, l2=0;

if ((l1 = php_strnlen(buffer+2, length-2)) > 0) {
exif_iif_add_tag(ImageInfo, SECTION_APP12, "Company", TAG_NONE, TAG_FMT_STRING, l1, buffer+2 TSRMLS_CC);
if (length > 2+l1+1) {
l2 = php_strnlen(buffer+2+l1+1, length-2-l1+1);
exif_iif_add_tag(ImageInfo, SECTION_APP12, "Info", TAG_NONE, TAG_FMT_STRING, l2, buffer+2+l1+1 TSRMLS_CC);
}
}
--- cut ---

Apparently, the author's intention was to restict the Info string length up to (SectionLength-sizeof(WORD)-
LengthOfCompany-sizeof('\0')), but instead a (length-2-l1+1) expression was used. This mistake can result in
reaveling 2 additional bytes present right after the 'Info' string being copied from the data file. However,
such a situation can only take place in case the string being copied is not terminated with a NULL byte -
the case of specially malformed files, only.


==[ Solution ]==

As for the first two vulnerabilities, some new check statements should be added in order to make sure every
memory byte being passed to the PHP script is really read from a file (existing checks could also be altered).
When it comes to the last one, the only thing to change is the '+' sign present inside the php_strlen() parameter.

Even though some of the vulnerabilities could theoretically result in Denial of Service conditions, it is
very unlikely to take place in practise, according to the authors' research.


==[ Proof of Concept ]==

The PoC file should be enclosed to this advisory file, in a ready-to-use form.


==[ Vendor Status ]==

The vendor has not yet been informed about this issue.


==[ Time line ]==

2009-08-10 : The vulnerability has been discovered
2009-xx-xx :


== Disclaimer ==

This document and all the information it contains is provided "as is",
without any warranty. Author is not responsible for the misuse
of the information provided in this advisory. The advisory is
provided for educational purposes only.

Permission is hereby granted to redistribute this advisory, providing
that no changes are made and that the copyright notices and
disclaimers remain intact.

------------------------------------------
Copyright (C) 2009 Hispasec Sistemas
------------------------------------------

Add a comment:

Nick:
URL (optional):
Math captcha: 8 ∗ 3 + 9 =