Skip to main content

Credential disclosure via cross-origin HTTP redirects

Publication Date 2026-09-25
Severity Moderate
Type Privilege Escalation
Affected PHP Versions
  • 7.2.0-7.2.34
  • 7.3.0-7.3.33
  • 7.4.0-7.4.33
  • 8.1.0-8.1.34
  • 8.2.0-8.2.33
  • 8.3.0-8.3.34
  • 8.4.0-8.4.25
  • 8.5.0-8.5.10
Fixed Product Versions
  • ZendPHP 7.2
  • ZendPHP 7.3
  • ZendPHP 7.4
  • ZendPHP 8.1
  • ZendPHP 8.2
  • ZendPHP 8.3
  • ZendPHP 8.4
  • ZendPHP 8.5
  • ZendServer 2021.4.8

CVE Details

When the http:// stream wrapper follows a redirect, it forwards the user-supplied Authorization, Cookie and Proxy-Authorization headers unchanged, even when the redirect target is a different host, a different port, or a downgrade from HTTPS to HTTP. A server that can steer a redirect therefore receives credentials that were only meant for the original origin. A malicious server capable of steering redirects can exploit this flaw to capture sensitive credentials intended only for the original destination.

Recommendations

Consider switching to cURL for making HTTP requests, instead of using the HTTP stream wrapper. If you cannot, consider manually following redirects and stripping these headers on any redirect:

function fetch(string $url, array $headers, int $maxRedirects = 5): string|false
{
    $origin = static fn (string $u): string => (static function (array $p): string {
        $port = $p['port'] ?? ($p['scheme'] === 'https' ? 443 : 80);
        return strtolower("{$p['scheme']}://{$p['host']}:{$port}");
    })(parse_url($u));

    $startOrigin = $origin($url);

    for ($i = 0; $i <= $maxRedirects; $i++) {
        $sendHeaders = $headers;
        if ($origin($url) !== $startOrigin) {
            $sendHeaders = array_filter(
                $headers,
                fn ($h) => ! preg_match('/^(authorization|cookie|proxy-authorization):/i', $h)
            );
        }

        $ctx = stream_context_create(['http' => [
            'header'          => implode("\r\n", $sendHeaders),
            'follow_location' => 0,
            'ignore_errors'   => true,
        ]]);

        $body   = file_get_contents($url, false, $ctx);
        $status = (int) substr($http_response_header[0] ?? '', 9, 3);

        if ($status < 300 || $status >= 400) {
            return $body;
        }

        foreach ($http_response_header as $h) {
            if (stripos($h, 'Location:') === 0) {
                // Note: resolve relative Location values against $url before using
                $url = trim(substr($h, 9));
                continue 2;
            }
        }
        return $body;
    }
    return false;
}

If you are using the HTTP stream wrapper in PHP, and send Authorization, Cookie, and/or Proxy-Authentication headers, we recommend updating to a patched version of PHP.