CVE-2023-3824
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 Phar extension, built by default, reads PHP archives as well as tar and zip archives, and its phar:// stream wrapper lets scripts open files and list directories inside them.
A Buffer Overflow vulnerability (CVE-2023-3824) has been identified in the phar_dir_read() function of PHP's Phar extension, which allows an attacker who supplies a phar archive with a very long entry name to overwrite memory on the stack, and to make PHP read past the end of a name, when an application lists a directory inside that archive. PHP tracks this issue as GHSA-jqcx-ccgc-xwhv.
Per MITRE CWE-121: A stack-based buffer overflow condition is a condition where the buffer being overwritten is allocated on the stack (i.e., is a local variable or, rarely, a parameter to a function).
This issue affects PHP before 8.0.30, PHP 8.1 before 8.1.22 and PHP 8.2 before 8.2.8, which includes every End-of-Life release line from PHP 5.6 through 7.4, each through its final release.
Details
Module Info
- Product: PHP
- Affected packages:
php-src(ext/phar) - Affected versions: <8.0.30, >=8.1.0 <8.1.22, >=8.2.0 <8.2.8
- GitHub repository: https://github.com/php/php-src
- Published packages: https://www.php.net/downloads
- Package manager: N/A
- Fixed in:
- OSS PHP 8.0.30, 8.1.22, 8.2.8
- NES for PHP 5.6.1 (based on PHP 5.6.40)
- NES for PHP 7.2.1 (based on PHP 7.2.34)
- NES for PHP 7.4.1 (based on PHP 7.4.33)
Vulnerability Info
This Critical-severity vulnerability is found in the Phar extension of PHP before 8.0.30, 8.1 before 8.1.22 and 8.2 before 8.2.8, including every End-of-Life release line from PHP 5.6 through 7.4, which never received the upstream fix. The PHP project scores it 9.4 under CVSS 3.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L).
When a script lists a directory inside a phar:// archive, for example with opendir() and readdir(), scandir() or DirectoryIterator, the Phar extension builds a list of entry names and phar_dir_read() copies each one into a php_stream_dirent record supplied by the caller. That record holds the name in a fixed buffer of MAXPATHLEN bytes. In PHP 7.4.33 the copy reads:
zend_hash_move_forward(data);
to_read = MIN(ZSTR_LEN(str_key), count);
if (to_read == 0 || count < ZSTR_LEN(str_key)) {
return 0;
}
memset(buf, 0, sizeof(php_stream_dirent));
memcpy(((php_stream_dirent *) buf)->d_name, ZSTR_VAL(str_key), to_read);
((php_stream_dirent *) buf)->d_name[to_read + 1] = '\0';The length check compares the name against the size of the whole record rather than leaving room for a terminator, and the terminator is written one byte too far, at to_read + 1 instead of to_read. A name of MAXPATHLEN - 1 bytes or more therefore writes past the end of the buffer. The record is usually a local variable of the calling function, so the write lands on the stack, which may lead to memory corruption or code execution. A name of exactly MAXPATHLEN bytes also leaves the buffer without any terminator, so readdir() can read past it and return adjacent memory.
Exploitation needs an application that lists a directory inside a phar:// archive that the attacker supplied, under any SAPI with the Phar extension enabled, which is the default. The phar.readonly setting does not prevent this, because the archive is only read. HeroDevs' analysis found the same code path in the PHP 5.6, 7.2 and 7.4 lines. Applications that never open archives from untrusted sources through phar:// are not exposed.
The fix accepts only a record of the expected size, skips names that do not fit with room for a terminator, and copies with PHP_STRLCPY(), which always terminates the string:
php_stream_dirent *dirent = (php_stream_dirent *) buf;
if (sizeof(dirent->d_name) <= ZSTR_LEN(str_key)) {
return 0;
}
memset(dirent, 0, sizeof(php_stream_dirent));
PHP_STRLCPY(dirent->d_name, ZSTR_VAL(str_key), sizeof(dirent->d_name), ZSTR_LEN(str_key));Mitigation
The PHP project publishes security fixes only for its supported release lines. PHP 5.6, 7.0, 7.1, 7.2, 7.3 and 7.4 are End-of-Life and will not receive any public updates to address this issue.
Until an upgrade is possible, do not open or list archives from untrusted sources through the phar:// wrapper, or disable the Phar extension if the application does not need it.
Users of the affected components should apply one of the following mitigations:
- Upgrade to PHP 8.0.30, 8.1.22, 8.2.8, or later.
- Leverage a commercial support partner like HeroDevs (NES for PHP) for post-EOL security support.
Credits
- Nora Dossche (finder, remediation)