Blog‎ > ‎

CVE-2015-3329: POC for buffer overflow in PHP phar_set_inode

posted Apr 18, 2015, 7:04 AM by Emmanuel Law
Background

PHP has the built-in Phar & PharData functionality since 5.3.0. These are used to manipulate the following archive types: tar,zip & phar. I found this vulnerability through fuzzing.


Technical Detail


This is a standard BOF in phar_set_inode() @ phar_internal.h:

static inline void phar_set_inode(phar_entry_info *entry TSRMLS_DC) /* {{{ */
{
	char tmp[MAXPATHLEN];
	int tmp_len;

	tmp_len = entry->filename_len + entry->phar->fname_len;
	memcpy(tmp, entry->phar->fname, entry->phar->fname_len);
	memcpy(tmp + entry->phar->fname_len, entry->filename, entry->filename_len);
	entry->inode = (unsigned short)zend_get_hash_value(tmp, tmp_len);
}

The vulnerability occurs because it didn't check that tmp_len is < MAXPATHLEN. On my x64bits ubuntu MAXPATHLEN=0x1000. 

Exploiting the vulnerability is trivial since attacker controls entry->filename and entry->filename_len. The only thing to note is that since tmp_len is below tmp[] on the stack, when overwriting tmp_len, it needs to ensure that the value being overwritten with is within reasonable limits, otherwise it would crash when zend_get_hash_value() is called.


There are multiple pathways to trigger this:
  • Parsing Tar
  • Parsing Zip
  • Parsing Phar
I've found that the easiest (read: boring) way to trigger it was via ZIP. The more interesting path would be via Tar for the following reasons:

  • The Tar header has a CRC check which makes it a slight pain during exploit development. However it can be bypassed because of this:
 102
 103
 115
 116
 117
 118
int phar_is_tar(char *buf, char *fname) /* {{{ */
{
......
	ret = (checksum == phar_tar_checksum(buf, 512));
.....
	if (!ret && strstr(fname, ".tar")) {
		/* probably a corrupted tar - so we will pretend it is one */
		return 1;
	}
}


Even though it validates the checksum (line 116), as long as the file ends with .tar (line 118), it would always pass the validation. Cheers PHP for being so lenient ;)

  • When exploiting a Zip, all you need for exploitation is a single entry with a long fname_len. Not for Tar because of stuff like the following in tar.c where entry.filename_len is limited to 256 (line 411), which is not enough to overflow the return address.
 394
 395
 396
 397
 398
 399
 400
 401
 402
403
404
405
406
407
408
409
410
411
char name[256];
int i, j;

for (i = 0; i < 155; i++) {
	name[i] = hdr->prefix[i];
	if (name[i] == '\0') {
		break;
	}
}
name[i++] = '/';
for (j = 0; j < 100; j++) {
	name[i+j] = hdr->name[j];
	if (name[i+j] == '\0') {
		break;
	}
}

entry.filename_len = i+j;
  • However by analysing the code path, it is still exploitation through the use of 2 tar entries (aka file header blocks): 
    • First a Longlink entry (EG: typeflag='L') to prep entry.filename_len to a large value. 
    • Followed by a traditional normal file entry
  • The best part of doing it this way is that you don't need to worry about entry check sums as it's done only on the 2nd entry. You can just grab it from any existing tar file and use it verbatim without having to calculate any checksum.
POC for exploits can be downloaded here. Written for:
  • x64 ubuntu
  • ./configure --enable-debug --enable-zip

Bug report can be found here
Comments