CVE-2017-9118

Heap-based Buffer Overflow
Affects
PHP (php-src)
in
PHP
No items found.
Versions
<7.4.27, >=8.0.0 <8.0.14, >=8.1.0 <8.1.1

Patch Available.

Exclamation circle icon
Patch Available

This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.

Overview

PHP is a general-purpose scripting language used mostly for web development. It runs behind web servers through server APIs such as Apache mod_php and PHP-FPM, and powers content management systems and frameworks such as WordPress, Drupal, Moodle and Laravel. Its PCRE extension provides regular expression functions such as preg_replace() and preg_replace_callback(), and is built into every PHP installation.

A Buffer Overflow vulnerability (CVE-2017-9118) has been identified in php_pcre_replace_impl(), the code behind PHP's preg_replace() and preg_replace_callback() functions, which allows an attacker who can make an application build a replacement result of extreme size to cause a heap out-of-bounds write, crashing the PHP process and possibly corrupting memory. PHP tracks this issue as bug #74604.

Per MITRE CWE-787: The product writes data past the end, or before the beginning, of the intended buffer.

This issue affects all versions of PHP before 7.4.27, PHP 8.0 before 8.0.14 and PHP 8.1 before 8.1.1, which includes the End-of-Life release lines PHP 5.6, 7.0, 7.1, 7.2 and 7.3 through their final releases. From PHP 7.0 on, only 32-bit builds can reach the flaw.

Details

Module Info

Vulnerability Info

This High-severity vulnerability is found in the pcre extension. The CVE record, assigned by MITRE, gives no CVSS score (NVD scores it 7.5 under CVSS 3.1, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) and no affected version range beyond the version the reporter tested (PHP 7.1.5), and the PHP project reclassified the report from a security issue to an ordinary bug. It was fixed upstream in PHP 7.4.27, 8.0.14 and 8.1.1. The End-of-Life PHP 5.6, 7.0, 7.1, 7.2 and 7.3 release lines never received that fix.

preg_replace() builds its result piece by piece: the text before each match, then the replacement, and finally the rest of the subject. It grows the result buffer as it goes and tracks how much it has written. In PHP 5.6 that count is an int, passed in and updated through the result_len pointer, while the buffer size is a size_t. In PHP 5.6.40 the copy step reads:

if (new_len + 1 > alloc_len) {
    new_buf = safe_emalloc(2, new_len + 1, alloc_len);
    alloc_len = 1 + alloc_len + 2 * (size_t)new_len;
    memcpy(new_buf, result, *result_len);
    efree(result);
    result = new_buf;
}
/* copy the part of the string before the match */
memcpy(&result[*result_len], piece, match-piece);
*result_len += match-piece;

Once the result passes 2 GiB, the int count wraps negative, and the next piece is written at that negative offset from the buffer, outside the allocation. PHP 7 tracks these lengths as size_t, but its overflow-checked size computation did not include the header that zend_string_alloc() adds to every string. A result length just under SIZE_MAX can therefore wrap when the header is added, so a buffer far smaller than requested is allocated and the copy that follows writes past it. Such lengths are only reachable on 32-bit builds.

Exploitation needs an application that runs preg_replace() or preg_replace_callback() with attacker-controlled input large enough to produce a result of that size. HeroDevs' analysis puts the PHP 5.6 threshold at a result over 2 GiB, which also needs memory_limit to be disabled or set above that size. On PHP 7.2 the flaw can only be reached on 32-bit builds.

The upstream fix accounts for the maximum zend_string header overhead when it computes the new buffer size, so the size can no longer wrap:

if (new_len >= alloc_len) {
    alloc_len = zend_safe_address_guarded(2, new_len, ZSTR_MAX_OVERHEAD) - ZSTR_MAX_OVERHEAD;
    if (result == NULL) {
        result = zend_string_alloc(alloc_len, 0);
    } else {
        result = zend_string_extend(result, alloc_len, 0);
    }
}

NES for PHP carries this fix to PHP 7.2. PHP 5.6 has its own form of the bug, so NES for PHP 5 computes each length as a size_t and refuses a result that would exceed 2 GiB with a "Result is too big" warning and a NULL result, before anything is copied.

Mitigation

The PHP project publishes security fixes only for its supported release lines. PHP 5.6, 7.0, 7.1, 7.2 and 7.3 are End-of-Life and will not receive any public updates to address this issue.

Until an upgrade is possible, keep memory_limit enabled and well below the sizes needed to reach the overflow, and limit the length of attacker-supplied input passed to preg_replace() and preg_replace_callback().

Users of the affected components should apply one of the following mitigations:

  • Upgrade to PHP 7.4.27, 8.0.14, 8.1.1, or later.
  • Leverage a commercial support partner like HeroDevs (NES for PHP) for post-EOL security support.

Credits

Vulnerability Details
Severity
Level
CVSS Assessment
Low
>=0 <4
Medium
>=4 <6
High
>=6 <8
Critical
>=8 <10
High
ID
CVE-2017-9118
PROJECT Affected
PHP (php-src)
Versions Affected
<7.4.27, >=8.0.0 <8.0.14, >=8.1.0 <8.1.1
NES Versions Affected
Published date
August 2, 2018
≈ Fix date
August 19, 2019
Fixed in
Category
Heap-based Buffer Overflow
Vex Document
Download VEXHow do I use it?
Sign up for the latest vulnerability alerts fixed in
PHP
Rss feed icon
Subscribe via RSS
or

By submitting the form I acknowledge receipt of our Privacy Policy.

Thanks for signing up for our Newsletter! We look forward to connecting with you.
Oops! Something went wrong while submitting the form.