CVE-2026-91766
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() fetch URLs over HTTP and HTTPS, sends any extra request headers set in the header option of the stream context, and follows redirects by default.
An Information Exposure vulnerability (CVE-2026-91766) has been identified in the redirect handling of PHP's HTTP stream wrapper, which allows a server that an attacker controls, or that redirects to one, to receive the Authorization, Cookie and Proxy-Authorization headers an application meant only for the original server, because the wrapper sends them again after a redirect to another host, port or scheme. PHP tracks this issue as GHSA-fpwc-w8rq-cr92.
Per MITRE CWE-522: The product transmits or stores authentication credentials, but it uses an insecure method that is susceptible to unauthorized interception and/or retrieval.
This issue affects all versions of PHP before 8.2.34, PHP 8.3 before 8.3.35, PHP 8.4 before 8.4.26 and PHP 8.5 before 8.5.11, which includes every End-of-Life release line from PHP 5.6 through 8.1, each up to its final release.
Details
Module Info
- Product: PHP
- Affected packages:
php-src(ext/standard) - Affected versions: <8.2.34, >=8.3.0 <8.3.35, >=8.4.0 <8.4.26, >=8.5.0 <8.5.11
- GitHub repository: https://github.com/php/php-src
- Published packages: https://www.php.net/downloads
- Package manager: N/A
- Fixed in:
- OSS PHP 8.2.34, 8.3.35, 8.4.26, 8.5.11
- 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 all PHP versions before 8.2.34, 8.3 before 8.3.35, 8.4 before 8.4.26 and 8.5 before 8.5.11, including every End-of-Life release line from PHP 5.6 through 8.1, which never received the upstream fix. The PHP project scores it 5.9 under CVSS 3.1 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N), and the CVE record lists both information exposure (CWE-200) and insufficiently protected credentials (CWE-522).
When the server answers with a redirect and follow_location is on, which is the default, the wrapper calls itself for the new URL and builds the request again from the same stream context, including every header in the header option. Before the fix, the only headers it removed on a redirect were the ones that describe a POST body. In PHP 7.4.33 that code reads:
if (!header_init) {
/* strip POST headers on redirect */
strip_header(user_headers, t, "content-length:");
strip_header(user_headers, t, "content-type:");
}
The redirected request carries no record of which server the credentials were meant for, so Authorization, Cookie and Proxy-Authorization go to whatever host, port or scheme the Location header names. The advisory compares this with the same class of issue that libcurl fixed in version 7.58.0 (CVE-2018-1000007). A redirect from HTTPS to HTTP also sends the credentials in cleartext over the network.
Exploitation needs an application that sends credentials in the header context option to an http:// or https:// URL with allow_url_fopen on and redirects followed, under any server API, where the server, or a service it redirects to, is not trusted with those credentials. The attacker can then collect a bearer token, session cookie or proxy credentials and reuse them against the original service. Applications that do not send credentials through the stream wrapper, or that set follow_location to 0, are not exposed.
The fix compares the scheme, host and port of each redirect target with the current request, filling in the default port, and on a mismatch strips the three credential headers from the request. The decision is sticky, so once a redirect leaves the origin the credentials stay withheld for every later redirect. The exception is Proxy-Authorization when a proxy is configured: that header is meant for the proxy rather than the target host, so it is still sent. The header removal code was also rewritten so that a repeated header, a folded continuation line or a header name inside another value cannot leave the real header behind. The redirect now reads:
int default_port = use_ssl ? 443 : 80;
bool same_origin = zend_string_equals_ci(resource->scheme, new_resource->scheme)
&& zend_string_equals_ci(resource->host, new_resource->host)
&& (resource->port ? resource->port : default_port)
== (new_resource->port ? new_resource->port : default_port);
...
int new_flags = HTTP_WRAPPER_REDIRECTED | (flags & HTTP_WRAPPER_STRIP_AUTH);
if (!same_origin) {
new_flags |= HTTP_WRAPPER_STRIP_AUTH;
}
On PHP 7.2 and 7.4, the fix builds on an earlier upstream change (php-src GH-11274), which the NES releases also carry. According to HeroDevs' analysis, that change also keeps POST and PATCH requests and their body on 307 and 308 redirects instead of switching to GET.
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, 8.0 and 8.1 are End-of-Life and will not receive any public updates to address this issue.
Until an upgrade is possible, set the follow_location context option to 0 for requests that carry credentials and handle redirects in application code.
Users of the affected components should apply one of the following mitigations:
- Upgrade to PHP 8.2.34, 8.3.35, 8.4.26, 8.5.11, or later.
- Leverage a commercial support partner like HeroDevs (NES for PHP) for post-EOL security support.
Credits
- q2a3z (finder)
- Ilia Alshanetsky (finder)
- Alexandre Daubois (remediation)