CVE-2025-1736
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 HTTP stream wrapper lets functions such as file_get_contents() and fopen() make HTTP and HTTPS requests, and the header option of an HTTP stream context adds request headers chosen by the script.
An Improper Input Validation vulnerability (CVE-2025-1736) has been identified in the way PHP's HTTP stream wrapper checks the user-supplied header context option for standard headers, which allows an attacker who can influence that option to make PHP omit headers it would otherwise send, such as the Basic authentication header built from the URL, or to have headers misinterpreted by the server. PHP tracks this issue as GHSA-hgf5-96fm-v528.
Per MITRE CWE-20: The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.
This issue affects PHP before 8.1.32, PHP 8.2 before 8.2.28, PHP 8.3 before 8.3.19 and PHP 8.4 before 8.4.5, which includes every End-of-Life release line from PHP 5.6 through 8.0, each through its final release.
Details
Module Info
- Product: PHP
- Affected packages:
php-src(ext/standard) - Affected versions: <8.1.32, >=8.2.0 <8.2.28, >=8.3.0 <8.3.19, >=8.4.0 <8.4.5
- GitHub repository: https://github.com/php/php-src
- Published packages: https://www.php.net/downloads
- Package manager: N/A
- Fixed in:
- OSS PHP 8.1.32, 8.2.28, 8.3.19, 8.4.5
- 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 Medium-severity vulnerability is found in the HTTP stream wrapper of PHP before 8.1.32, 8.2 before 8.2.28, 8.3 before 8.3.19 and 8.4 before 8.4.5, including every End-of-Life release line from PHP 5.6 through 8.0, which never received the upstream fix. The PHP project scores it 6.3 under CVSS 4.0 (CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N).
Before the wrapper adds its own Authorization, Host, User-Agent, From, Content-Length, Content-Type and Connection headers, it looks for each of them in the script's header option and skips the ones the script already provides. HTTP separates header lines with a carriage return and line feed (CRLF), but the check accepted a header name at the start of the block or after a bare line feed. In PHP 7.4.33 it reads:
static zend_bool check_has_header(const char *headers, const char *header) {
const char *s = headers;
while ((s = strstr(s, header))) {
if (s == headers || *(s-1) == '\n') {
return 1;
}
s++;
}
return 0;
}A header string such as X: a, a bare line feed, then Authorization: x therefore counts as already holding an Authorization header, even though a server that splits headers on CRLF reads that text as part of the X header. The wrapper then omits the Basic authentication header it would have built from the credentials in the URL, and other standard headers can be suppressed or misread in the same way. HeroDevs' analysis found that PHP 5.6 and 7.2 performed this check inline and also accepted a match after a space or a tab, so on those lines the NES fix also brings in the stricter check that PHP added in 7.3.
Exploitation needs an application that passes attacker-influenced text into the header option of an HTTP stream context and requests an http or https URL through the wrapper, with allow_url_fopen enabled, under any SAPI. Applications that build the header option only from trusted values are not exposed.
The fix only accepts a match at the start of the header block or right after a CRLF:
static bool check_has_header(const char *headers, const char *header) {
const char *s = headers;
while ((s = strstr(s, header))) {
if (s == headers || (*(s-1) == '\n' && *(s-2) == '\r')) {
return 1;
}
s++;
}
return 0;
}Mitigation
The PHP project publishes security fixes only for its supported release lines. PHP 5.6, 7.0, 7.1, 7.2, 7.3, 7.4 and 8.0 are End-of-Life and will not receive any public updates to address this issue.
Until an upgrade is possible, remove carriage return and line feed characters from any untrusted value before placing it in the header option of an HTTP stream context.
Users of the affected components should apply one of the following mitigations:
- Upgrade to PHP 8.1.32, 8.2.28, 8.3.19, 8.4.5, or later.
- Leverage a commercial support partner like HeroDevs (NES for PHP) for post-EOL security support.
Credits
- Jakub Zelenka (finder, remediation)