Listen to this Post
Guzzle, a popular PHP HTTP client, is vulnerable to a host-based security check bypass due to a divergence between how Guzzle processes request URIs and how its underlying transport handlers (cURL and stream wrappers) resolve those same URIs. When an application performs security checks on a hostname—such as denylisting private IP ranges or verifying domain allowlists—it may inspect the URI as a string. However, when the request is handed to the transport layer, libcurl or `fopen()` may parse, percent-decode, or apply IDNA mapping to the host, resulting in a connection to a destination the application intended to block.
The vulnerability stems from Guzzle giving the transport the request URI as text and supplying the `Host` header separately. The cURL handlers set `CURLOPT_URL` to the URI exactly as written and push that `Host` into CURLOPT_HTTPHEADER; `StreamHandler` does the same through fopen(). libcurl then parses the authority itself, percent-decoding it and, on an IDN-capable build, applying IDNA mapping, and uses the result to resolve, connect, name the TLS peer, and address a proxy CONNECT, while the supplied `Host` suppresses the aligned one libcurl would have generated.
For example, in http://127.0.0.%31/`, the URI host is one that `filter_var()` rejects as an IP literal, yet libcurl decodes it to `127.0.0.1` and reaches loopback with no DNS lookup, while the server receivesHost: 127.0.0.%31. An attacker who influences a fetched URI can therefore reach a host the application's checks excluded and read whatever the host exposes of the response.Cookie
The same divergence moves Guzzle's own decisions onto a spelling the transport does not use: `no_proxy` selects proxy routing from the literal host, and `RedirectMiddleware` decides from it whether to strip `Authorization` and. The cookie middleware extracts `Set-Cookie` against the URI Guzzle produced, not the authority contacted, so for a raw divergent URI the cookie is stored under the URI host as written. Where Guzzle rewrote that URI but left a divergentHost, or where the caller supplied one, the cookie is stored under the canonical name and replayed by ordinary later requests to it.UriInterface
With a third-party, a host of `[email protected]` reaches `127.0.0.1` through all three handlers and generates `Authorization: Basic` from userinfo the application never wrote. Exploitation requires the application to build a request URI from untrusted input and to make a host decision before handing it to Guzzle. Applications that only fetch URIs they construct themselves are not affected, and an exact allowlist of canonical names ordinarily fails closed; the exposure is to denylists, private-range and IP-literal checks, and any check that treats an unresolvable name as safe.idn_conversion
The raw Unicode class needs an IDNA transformation somewhere: either a libcurl built with IDN support or Guzzle's own, off by default on both branches, which rewrites the URI in `Client::buildUri()` before a handler sees it and leaves a prebuilt request's explicit `Host` as written, while a request the client builds derives that header from the rewritten URI and produces no divergence. Noncanonical numeric spellings such as127.1,2130706433,0x7f000001, and `0177.0.0.1` remain accepted after the patch and reach whatever the transport reads them as, loopback or a routable public host, and the cURL and stream handlers can differ, so a check comparing a host against an address as text stays bypassable. Guzzle does not offer SSRF protection, and neither cache poisoning nor cross-tenant compromise was established.
<h2 style="color: blue;">DailyCVE Form:</h2>
Platform: Guzzle (PHP HTTP client)
Version: < 7.15.2, 8.0.0
Vulnerability: Host validation bypass (SSRF)
Severity: Critical
Date: 2026-08-03
<h2 style="color: blue;">Prediction: 2026-07-26 (7.15.2/8.0.1)</h2>
<h2 style="color: blue;">What Undercode Say:</h2>
<h2 style="color: blue;">Check your Guzzle version:</h2>
composer show guzzlehttp/guzzle
<h2 style="color: blue;">Update to patched version:</h2>
composer require guzzlehttp/guzzle:^7.15.2 or for 8.x composer require guzzlehttp/guzzle:^8.0.1
<h2 style="color: blue;">Workaround validation snippet (before passing URI to Guzzle):</h2>
$host = $uri->getHost();
if (
preg_match('/\A[\x21-\x7E]\z/D', $host) !== 1
|| strpbrk($host, '%@/?\\') !== false
|| substr($host, -1) === '.'
) {
throw new RuntimeException('Refusing to fetch this URI host.');
}
// Also validate explicit Host header
if (
preg_match('/\A[\x21-\x7E]\z/D', $hostHeader) !== 1
|| strpos($hostHeader, '%') !== false
) {
throw new RuntimeException('Refusing to send this Host header.');
}
<h2 style="color: blue;">Exploit:</h2>
An attacker can provide a non-canonical URI such as:
-http://127.0.0.%31/` – passes `filter_var()` IP literal check but libcurl resolves to `127.0.0.1`
– http://[email protected]/` – reaches `127.0.0.1` while application sees `blocked.example.com`no_proxy`)
- `http://0x7f000001/` – decimal/hex/octal IP variants that bypass string-based checks
- `http://example.com./` – trailing dot may be accepted by application but resolved differently
The transport connects to the decoded/interpreted host, while Guzzle's own logic (proxy routing, cookie scope, redirect decisions) operates on the literal host string, enabling SSRF, proxy bypass, and cookie mis-scoping.
<h2 style="color: blue;">Protection:</h2>
- Upgrade to Guzzle 7.15.2 or 8.0.1 immediately. These versions validate the request host in all three built-in handlers before any network I/O, rejecting hosts with non-printable ASCII, percent escapes, URI authority delimiters, unbalanced brackets, or numeric-looking parts with trailing dots.
- If upgrade is not possible, implement the workaround validation snippet above before passing any URI to Guzzle, and re-validate on every redirect hop.
- Do not rely on `filter_var()` with `FILTER_VALIDATE_IP` for security decisions—it rejects many forms that transports accept.
- Resolve the host to an IP address and check against an allowlist/denylist of actual addresses, rather than relying on hostname string comparisons.
- Use a separate cookie jar for untrusted origins.
- Consider network-level restrictions (firewalls, network namespaces) to limit outbound reach.
<h2 style="color: blue;">Impact:</h2>
Successful exploitation allows an attacker to bypass application-level host validation, potentially leading to Server-Side Request Forgery (SSRF). This can enable:
- Access to internal services, metadata endpoints, or administrative interfaces
- Reading sensitive data from internal systems
- Bypassing proxy routing decisions (
– Cookie scope confusion, potentially leading to cookie replay or session fixation
– Authorization header leakage in certain redirect scenarios
While the vulnerability does not directly provide remote code execution, it serves as a critical primitive for SSRF attacks, especially in environments where Guzzle is used to fetch user-supplied URLs. The ease of crafting non-canonical hostnames makes this a high-risk issue for services relying on Guzzle for third-party requests.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

