|
| 1 | +# Out-of-Bounds read in ``urldecode()`` |
| 2 | + |
| 3 | +## Details |
| 4 | +* **Description**: "*In PHP versions 7.2.x below 7.2.30, 7.3.x below 7.3.17 and 7.4.x below 7.4.5, if PHP is compiled with EBCDIC support, ``urldecode()`` function can be made to access locations past the allocated memory, due to erroneously using signed numbers as array indexes.*" - In other words, if you type a negative hex value in a system with EBCDIC encoding enabled, you'll be able to leak memory ✨ |
| 5 | + |
| 6 | +* **CVSS3 Score**: 7.5 high |
| 7 | + |
| 8 | +## Impact / Threat |
| 9 | +This bug requires the vulnerable server to enable EBCDIC encoding, so it mainly affects Mainframe systems(yuck!) **and** possibly some flavors of IBM Cloud Instances which runs PHP (IBM created this encoding so they have support in their cloud environment environment as well. It was found to be enabled in cloud "flavors" such as *Linux z/OS*. References: [[#1]](https://cloud.ibm.com/docs/runtimes/php?topic=PHP-getting_started), [[#2]](https://www.ibm.com/support/knowledgecenter/zosbasics/com.ibm.zos.zappldev/zappldev_14.htm), [[#3]](https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.gxla100/encodesupport.htm)). |
| 10 | + |
| 11 | + |
| 12 | +## The bug / Exploitation |
| 13 | + |
| 14 | +This is the source code of ``urldecode()``: |
| 15 | + |
| 16 | +[ext/standard/url.c](http://git.php.net/?p=php-src.git;a=blob;f=ext/standard/url.c;h=fe6d7f9de1d69eaafd577518d92d899feb7145b0;hb=fe6d7f9de1d69eaafd577518d92d899feb7145b0#l548) @ line 548 |
| 17 | + |
| 18 | +```c |
| 19 | +/* {{{ php_url_decode |
| 20 | + */ |
| 21 | +PHPAPI size_t php_url_decode(char *str, size_t len) |
| 22 | +{ |
| 23 | + char *dest = str; |
| 24 | + char *data = str; |
| 25 | + |
| 26 | + while (len--) { |
| 27 | + if (*data == '+') { |
| 28 | + *dest = ' '; |
| 29 | + } |
| 30 | + else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) |
| 31 | + && isxdigit((int) *(data + 2))) { |
| 32 | +#ifndef CHARSET_EBCDIC |
| 33 | + *dest = (char) php_htoi(data + 1); |
| 34 | +#else |
| 35 | + *dest = os_toebcdic[(char) php_htoi(data + 1)]; |
| 36 | +#endif |
| 37 | + data += 2; |
| 38 | + len -= 2; |
| 39 | + } else { |
| 40 | + *dest = *data; |
| 41 | + } |
| 42 | + data++; |
| 43 | + dest++; |
| 44 | + } |
| 45 | + *dest = '\0'; |
| 46 | + return dest - str; |
| 47 | +} |
| 48 | + |
| 49 | +``` |
| 50 | +
|
| 51 | +If ``CHARSET_EBCDIC`` is defined (usually, on systems with EBCDIC encoding support), an Out-of-Bounds Read can occur. |
| 52 | +
|
| 53 | +Let's focus on the relevant parts of the function: |
| 54 | +```c |
| 55 | +PHPAPI size_t php_url_decode(char *str, size_t len) |
| 56 | +{ |
| 57 | + char *dest = str; |
| 58 | + char *data = str; |
| 59 | +/*...more code...*/ |
| 60 | +
|
| 61 | +#ifndef CHARSET_EBCDIC |
| 62 | + *dest = (char) php_htoi(data + 1); |
| 63 | +#else |
| 64 | + *dest = os_toebcdic[(char) php_htoi(data + 1)]; // <--- oob read here |
| 65 | +#endif |
| 66 | +
|
| 67 | +/* ... more code ... */ |
| 68 | +``` |
| 69 | +* ``os_toebcdic[256]`` is an array(or a map, i assume) used for decoding purposes. |
| 70 | +* To convert the string input into hex values, PHP uses ``php_htoi()`` and then convert the result into a signed byte(char). |
| 71 | +* This signed number is then provided as an index to the ``os_toebcdic[]`` array. |
| 72 | +* There will be no OOB Read after the buffer because the max value of a byte is 0xff (==256), which is the same size as the ``os_toebcdic[]`` |
| 73 | +* However, the casting (in the second bullet) is done to a ``char`` and not an ``unsigned char``, which means that we can insert **negative hex values** to leak values that are found in the memory **BEFORE** our buffer. |
| 74 | + |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | +payload: |
| 79 | +```php |
| 80 | +<? |
| 81 | +urldecode('%xfd'); //0xfd == -3, could be 0x80 for bigger OOB (which is -128 in dec, this is the minimum value of a signed byte. b10000000) |
| 82 | +?> |
| 83 | +``` |
| 84 | + |
| 85 | +in gdb: |
| 86 | + |
| 87 | +``` |
| 88 | +gdb-peda$ call php_htoi(data+1) |
| 89 | +$42 = 0xfd |
| 90 | +
|
| 91 | +gdb-peda$ p/d (char)$42 |
| 92 | +$43 = -3 |
| 93 | +``` |
| 94 | +The array index is negative an memory will be leaked via the returned value of PHP's ``urldecode()`` |
| 95 | + |
| 96 | +## Understanding the Concept |
| 97 | + |
| 98 | +Let's take the following example program: |
| 99 | +```c |
| 100 | +#include<stdio.h> |
| 101 | + |
| 102 | +int main() |
| 103 | +{ |
| 104 | + char text[255] = "ABCD"; |
| 105 | + char buf[] = "QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ"; // not used in the program |
| 106 | + |
| 107 | + char c = 0xfc; // signed char, can contain negative values (from -128 to 255) |
| 108 | + |
| 109 | + printf("%x", text[c]); // text[-3] |
| 110 | + return 0; |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +We are using ``c`` as an index specifier to ``text`` and print the value in hex. |
| 115 | + |
| 116 | +The expected output will be a character from the ``text[]`` array, maybe A(``0x41``) or B(``0x42``), or maybe C(``0x43``), etc. Even if the attacker will manage to read **after** ``D``, he will not be able to read the contents of what is after ``text[255]`` in memory (because the maximum size of ``char c`` is 255). So what do we do? insert a negative value :D |
| 117 | + |
| 118 | +The actual output is: ``0x51``, which is Q, and belongs to anohter variable in the program (``buf``). |
| 119 | +It happens when an array index specifier is set to a negative value. In our case, ``c`` was set to ``0xfc`` (which is ``-3`` in dec) |
| 120 | + |
| 121 | +In the following hexdump, you can see that ``buf`` is found right before our ``text[]`` array in the memory: |
| 122 | +``` |
| 123 | +gdb-peda$ hexdump &buf |
| 124 | +
|
| 125 | ++0000 0xbffff4c5 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 │QQQQ│QQQQ│QQQQ│QQQQ│ |
| 126 | +... |
| 127 | ++0020 0xbffff4e5 51 51 51 51 51 51 51 51 51 51 00 41 42 43 44 00 │QQQQ│QQQQ│QQ.A│BCD.│ |
| 128 | ++0030 0xbffff4f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│ |
| 129 | +pwndbg> |
| 130 | ++0040 0xbffff505 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │....│....│....│....│ |
| 131 | +... |
| 132 | +``` |
| 133 | + |
| 134 | +Hence, we jumped "backwards" when we inserted negative values in the array index specifier. |
| 135 | + |
| 136 | +## The Fix |
| 137 | + |
| 138 | +The PHP Development team turned the array index specifier to be an ``unsigned char`` to avoid negative values in array index specifiers. |
| 139 | + |
| 140 | +Same issue was found in ``php_raw_url_decode`` |
| 141 | + |
| 142 | +The commit: http://git.php.net/?p=php-src.git;a=blobdiff;f=ext/standard/url.c;h=1dd073e2bb423652821f351135b9582d76e175d5;hp=fe6d7f9de1d69eaafd577518d92d899feb7145b0;hb=9d6bf8221b05f86ce5875832f0f646c4c1f218be;hpb=14fcc813948254b84f382ff537247d8a7e5e0e62 |
| 143 | + |
| 144 | + |
| 145 | + |
| 146 | + |
| 147 | +>Original report #79465: https://bugs.php.net/bug.php?id=79465&edit=2 |
0 commit comments