Aug 4, 2026·7 min read·3 visits
Guzzle's cookie matching logic failed to identify noncanonical IPv4 host formats (like hexadecimal, octal, or percent-encoded) as IP literals, incorrectly applying standard suffix/subdomain matching and leaking sensitive cookies to attacker-controlled subdomains.
A vulnerability in the Guzzle HTTP client allows session identifiers, auth tokens, or cookies to be leaked to unauthorized hosts due to incorrect cookie domain validation of noncanonical IPv4 host formats. Guzzle failed to recognize octal, hexadecimal, and percent-encoded IP addresses as IP literals, treating them as standard domains and incorrectly extending their scope to subdomains.
The Guzzle HTTP client is a standard library used in PHP web applications to perform HTTP requests. A critical responsibility of any HTTP client with cookie management enabled (a Cookie Jar) is the isolation of sensitive state across origin boundaries. When managing session cookies, client-side libraries must adhere strictly to domain matching specifications to ensure cookies set by one domain are not transmitted to untrusted hosts.
The vulnerability identified as CVE-2026-69245 belongs to the CWE-180 (Incorrect Behavior Order: Validate Before Canonicalize) and CWE-346 (Origin Validation Error) classes. It represents a systematic failure in how Guzzle handles the scopes of domains that are actually IP address literals or numeric hosts. Standard cookies set for an IP address must never be matched against subdomains, because numeric IP addresses do not have hierarchical subdomains.
Prior to the patches, Guzzle's cookie domain matching routine incorrectly recognized noncanonical IPv4 host representations, such as hexadecimal or octal encodings, as valid domain suffixes rather than IP literals. Consequently, when Guzzle processed an outbound request to an attacker-controlled subdomain appended to a noncanonical IP string, it incorrectly attached cookies associated with the IP address. This led to potential cross-subdomain session leaking and cookie injection.
The fundamental security boundary for cookies on numeric IP hosts is defined in RFC 6265, Section 5.1.3. This standard dictates that a cookie's Domain attribute must match the request host exactly if the host is identified as an IP address. Suffix-based matching, which permits standard domains to share cookies with their subdomains (e.g., example.com and sub.example.com), must be disabled when the host is a numeric IP.
In Guzzle's legacy codebase, the verification of whether a host represents an IP literal was confined to a simplistic check in the SetCookie::matchesDomain() function. This code split the host string by dot characters and verified if the final label consisted entirely of digits using PHP's ctype_digit(). While this accurately identified canonical dotted-decimal IPv4 formats (e.g., 127.0.0.1), it was unable to identify noncanonical formats that underlying network transports accept.
Operating system DNS resolvers and standard libraries such as libcurl parse hosts using inet_aton-compatible rules, which accept hexadecimal (e.g., 0x7f000001), octal (e.g., 0177.0.0.1), mixed-base representation, and percent-encoded IP addresses. Because Guzzle's simple digit check did not recognize these alternative string patterns as IP literals, Guzzle fallback-validated the host as a hierarchical domain. An attacker could register or construct a hostname like evil.0x7f000001 which Guzzle's domain matcher would treat as a valid subdomain of the domain 0x7f000001, leading to scope leakage.
To understand the mechanics of the vulnerability, we analyze the structural changes implemented in the Guzzle codebase. Prior to Guzzle 7.15.2 and 8.0.1, the SetCookie::matchesDomain() function determined IP literal status using only basic string separation.
Here is the vulnerable logic inside src/Cookie/SetCookie.php:
// Vulnerable Guzzle code
$labels = \\explode('.', $host);
$last = (string) \\end($labels);
if ($last !== '' && \\ctype_digit($last)) {
// Correctly identifies canonical IPv4, but fails for noncanonical formats
return false; // Prevent suffix matching for canonical IP
}The patch introduces a dedicated validator class and refactors matchesDomain() to reject percent-encoded cookie domains and utilize strict parsing for alternate formats:
// Patched Guzzle code in src/Cookie/SetCookie.php
public function matchesDomain(string $domain): bool
{
// ...
// Reject percent-escaped domains from wildcard matches
if (\\strpos($cookieDomain, '%') !== false) {
return false;
}
// Utilize the new HostValidator to evaluate IP and numeric host formats
if (self::isIpAddressOrNumericHost($cookieDomain)) {
return false;
}
// ...
}
private static function isIpAddressOrNumericHost(string $host): bool
{
$labels = \\explode('.', $host);
$last = (string) \\end($labels);
if ($last !== '' && \\ctype_digit($last)) {
return true;
}
// Evaluate against the transport's decimal, octal, and hexadecimal inet_aton grammar
return HostValidator::isNumericIpv4Host(\\rtrim($host, '.'));
}The introduction of src/Handler/HostValidator.php provides a systematic way to validate request hosts before they reach the transport layer. The function isNumericIpv4Host() splits the incoming host and validates each octet structure. If the octet matches a hex format (e.g., starting with 0x or 0X), it verifies the characters via standard hexadecimal character maps. If it matches octal format (starting with 0), it restricts the character range to octal digits.
Exploitation of this vulnerability requires that an application using Guzzle has enabled a persistent cookie jar and initiates HTTP requests targeting noncanonical representations of an IP address. The attack can proceed in two primary ways: session exfiltration (information disclosure) and session fixation.
In a session exfiltration attack, the target client is directed to make a request to a canonical host represented noncanonically, such as http://0x7f000001/ (resolving to local host). The responding server sets a sensitive cookie with Domain=0x7f000001. Because Guzzle fails to identify the host as an IP, it registers the cookie with suffix-matching capabilities. When the client subsequently makes a request to http://evil.0x7f000001/, Guzzle's domain scope matching incorrectly permits the transmission of the 0x7f000001 cookie to the attacker-controlled server.
In a session fixation scenario, the client is directed to the malicious domain http://evil.0x7f000001/. The attacker's server responds by setting a cookie with Domain=0x7f000001 containing a pre-generated session ID. Guzzle accepts this cookie because it treats 0x7f000001 as a regular top-level domain. When the client subsequently accesses http://0x7f000001/, Guzzle transmits the fixed session identifier, allowing the attacker to intercept and control the session state.
While the patches implemented in versions 7.15.2 and 8.0.1 significantly decrease the attack surface, parser differences between the verification layer and the underlying transport layer may still yield edge-case exploitation pathways.
One potential gap involves IDN-capable transports. Many modern systems compile PHP's Curl extension with support for Internationalized Domain Names (IDN) via libidn2 or similar libraries. If a Guzzle client makes a request to a host using Unicode Fullwidth digits (e.g., 127.0.0.1 where 1 is U+FF11), Guzzle's HostValidator checks for printable ASCII and redirects the URI parsing through the IDN conversion middleware if enabled. If the domain matching routine evaluates the unnormalized host string prior to canonicalization, Guzzle may treat it as a domain name, while the underlying curl transport normalizes and resolves it directly to the IP literal 127.0.0.1.
Another gap is the variation of inet_aton behaviors across target operating systems. While Guzzle's isNumericIpv4Host() strictly parses standard decimal, octal, and hexadecimal formats, certain operating systems handle extreme cases—such as integer overflows in octal fields or single-part integer hosts (e.g., 2130706433 which resolves to 127.0.0.1)—in inconsistent ways. If Guzzle classifies a single-part integer as a regular domain name but the OS resolver translates it to an IP address, cookie scope mismatch can still occur.
Mitigation of CVE-2026-69245 requires upgrading Guzzle dependencies to versions that contain the host validation and strict IP parsing logic. Applications on the Guzzle 7.x release branch must be updated to at least 7.15.2, and applications on the 8.x branch must be updated to at least 8.0.1. These updates are available through standard PHP Composer package installations.
To detect historical or active exploitation attempts at the network proxy or web application firewall layer, security teams can implement regular expressions designed to catch noncanonical IP formats within Host headers. WAF rules should check for hexadecimal patterns like 0x followed by hexadecimal digits within HTTP headers. Additionally, check for octal representations where octets start with a leading zero and are followed exclusively by octal digits 0-7.
At the application level, developers must ensure that any user-supplied IP addresses or host configurations are fully validated using native PHP structures before being passed into Guzzle requests. Using filter_var($host, FILTER_VALIDATE_IP) allows the application to discard noncanonical host strings entirely or normalize them to standard decimal format before Guzzle initiates the HTTP request lifecycle.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Guzzle Guzzle | < 7.15.2 | 7.15.2 |
Guzzle Guzzle | >= 8.0.0, < 8.0.1 | 8.0.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-180, CWE-346, CWE-384 |
| Attack Vector | Network (AV:N) |
| Attack Complexity | Low (AC:L) |
| CVSS Severity | 6.5 Medium |
| EPSS Score | Not Available |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
The application parses or verifies cookie domains using raw string values before resolving noncanonical representations (e.g., hexadecimal or percent-encoded) which the transport layer subsequently canonicalizes or decodes.
CVE-2026-59881 is a protocol compliance and input validation vulnerability in the client-side WebSocket implementation of the aiohttp asynchronous HTTP client/server framework for Python. Prior to version 3.14.2, the framework's parser unexpectedly accepts and attempts to decompress frames containing the RSV1 bit, even when the permessage-deflate extension has not been negotiated during the initial WebSocket handshake. This violation of RFC 6455 allows a malicious or compromised server to bypass client configuration, forcing decompression routines that can lead to high CPU and memory consumption, resulting in a denial-of-service condition.
An asynchronous HTTP client/server framework for asyncio and Python, aiohttp prior to version 3.14.2 is vulnerable to HTTP Request Smuggling. The server-side HTTP parser immediately transitions the protocol state to 'upgraded' upon receiving a WebSocket upgrade request before consuming the accompanying request body. If the backend handler rejects the upgrade request while keeping the TCP connection alive, the unconsumed request body remains in the socket buffer and is parsed as a subsequent pipelined HTTP request. This allows an attacker to smuggle requests, bypass frontend reverse proxy controls, and perform unauthorized actions.
CVE-2026-69246 is a host validation bypass vulnerability in the Guzzle PHP HTTP client. The flaw resides in Guzzle's core HTTP transport handlers (cURL and PHP stream wrappers). Under specific conditions, a parser differential occurs between the host validation layer and the underlying network transport library (e.g., libcurl), allowing remote attackers to bypass SSRF filters, proxy routing rules, and redirect protections via crafted noncanonical URI representations.
A side-channel vulnerability in pyca/cryptography (versions 44.0.0 through 49.9.9) allows unauthenticated remote attackers to expose a Bleichenbacher oracle. This flaw exists within the PKCS#7 decryption module (specifically pkcs7_decrypt_der, pkcs7_decrypt_pem, and pkcs7_decrypt_smime) during Content Encryption Key (CEK) decryption when using RSA PKCS#1 v1.5 padding. Differences in error classification and symmetric execution timing allow an attacker to reconstruct plaintext keys.
An uncontrolled resource consumption vulnerability (CWE-400) exists in the python-cryptography library's Rust-based X.509 verification engine. The flaw allows unauthenticated remote attackers to trigger severe CPU exhaustion and Denial of Service (DoS) by supplying specially crafted certificate chains containing duplicate self-signed certificates, forcing the recursive path builder into an exponential state-search loop.
An improper certificate validation vulnerability (CWE-295) in the Rust-based X.509 verification engine of python-cryptography allows wildcard Subject Alternative Names (SANs) to bypass permitted Name Constraints. This enables an attacker to construct certificates that escape the restricted scope of a subordinate Certificate Authority (CA) and successfully authenticate against vulnerable client installations. The vulnerability is tracked as CVE-2026-69248 and GHSA-m2h6-j472-rp4c, with a CVSS v4.0 base score of 6.9.