CVE-2026-69246
PHP vulnerability analysis and mitigation

Overview

CVE-2026-69246 is a URI parsing divergence vulnerability in Guzzle (guzzlehttp/guzzle), a popular PHP HTTP client, that allows attackers to bypass host-based validation checks via percent-encoded or noncanonical characters in request URIs. The flaw affects all versions prior to 7.15.2 and version 8.0.0 (prior to 8.0.1). It was disclosed on July 26, 2026, with the GitHub Advisory (GHSA-v5mv-p594-2x33) published on August 3, 2026. The vulnerability carries a CVSS v3.1 base score of 7.2 (High) (GitHub Advisory).

Technical details

The root cause is an interpretation conflict (CWE-436) combined with incorrect validate-before-canonicalize ordering (CWE-180), leading to Server-Side Request Forgery (CWE-918). Guzzle passes the raw request URI text to its transport handlers (cURL and StreamHandler) while supplying the Host header separately. libcurl independently parses and percent-decodes the URI authority — for example, 127.0.0.%31 is decoded to 127.0.0.1 and used for the actual TCP connection — while Guzzle's own host checks, no_proxy routing decisions, and RedirectMiddleware authorization-stripping logic all operate on the literal, undecoded URI text. This means PHP's filter_var() rejects 127.0.0.%31 as an invalid IP literal, but libcurl silently connects to loopback. Additionally, a third-party UriInterface carrying a userinfo-embedded host like blocked.example.com@127.0.0.1 reaches 127.0.0.1 through all three handlers and can fabricate an Authorization: Basic header from userinfo the application never wrote. Cookie scope is also affected: cookies stored under a percent-escaped domain (e.g., Domain=127.0.0.%31) were not restricted to exact-match-only, enabling cookie leakage to look-alike hosts (GitHub Advisory, PR #3907, PR #3908).

Impact

Successful exploitation allows an attacker who can influence a URI fetched by a Guzzle-based application to reach internal or loopback hosts that the application's denylist or IP-range checks were intended to block, effectively achieving SSRF. The attacker can read responses from those internal hosts, potentially exposing sensitive data such as cloud metadata endpoints, internal APIs, or administrative interfaces. Additionally, Authorization and Cookie headers may not be stripped on redirects as intended, leading to credential leakage across trust boundaries, and cookies may be replayed to unintended hosts due to the cookie-scope bypass (GitHub Advisory).

Exploitation steps

  1. Identify a vulnerable target: Find a PHP application using Guzzle < 7.15.2 or Guzzle 8.0.0 that accepts user-supplied URLs or URL components and uses them to make outbound HTTP requests with host-based access controls (e.g., blocking 127.0.0.1 or RFC 1918 ranges).
  2. Craft a percent-encoded host: Construct a URI where the host is written in a noncanonical form that bypasses the application's validation but is decoded by libcurl to a blocked address — for example, http://127.0.0.%31/admin (decoded by libcurl to http://127.0.0.1/admin) or http://0x7f000001/ for loopback.
  3. Submit the malicious URI: Supply the crafted URI as input to the vulnerable application endpoint (e.g., a webhook URL field, a URL-fetching API parameter, or a proxy target).
  4. Bypass host validation: The application's filter_var() or regex check rejects or ignores the percent-encoded host as an invalid IP literal, while Guzzle passes the raw URI to libcurl, which decodes it and connects to the actual loopback or internal address.
  5. Read the response: The application receives and potentially returns or processes the response from the internal host (e.g., cloud metadata at 169.254.169.254, internal admin panels, or other SSRF-reachable services), which the attacker can then read.
  6. Optional — cookie/credential leakage: If the application follows redirects, craft a redirect chain where the RedirectMiddleware fails to strip Authorization or Cookie headers because it compares the literal (undecoded) host rather than the resolved one, leaking credentials to the attacker-controlled destination (GitHub Advisory, PR #3907).

Indicators of compromise

  • Network: Outbound HTTP/HTTPS requests from the application server to loopback (127.0.0.1) or RFC 1918 addresses (e.g., 10.x.x.x, 172.16-31.x.x, 192.168.x.x) or cloud metadata endpoints (169.254.169.254) that are unexpected for normal application behavior.
  • Network: HTTP requests with a Host header containing percent-encoded characters (e.g., Host: 127.0.0.%31) in web server or proxy access logs.
  • Logs: Application logs showing Guzzle requests to URIs containing percent-encoded octets in the host component (e.g., %31, %32, etc.) or userinfo-embedded hosts (e.g., blocked.example.com@<ip>).
  • Logs: Unexpected HTTP responses from internal services being processed by the application, particularly from metadata endpoints or administrative interfaces.
  • Logs: RequestException messages referencing host validation failures after upgrading to patched versions — these may indicate prior exploitation attempts that are now being blocked.

Mitigation and workarounds

Upgrade to Guzzle 7.15.2 (for the 7.x branch) or 8.0.1 (for the 8.x branch), which add a new HostValidator class that validates the request host in all three built-in handlers (CurlHandler, CurlMultiHandler, StreamHandler) before any network I/O, rejecting hosts with percent escapes, non-printable ASCII, URI authority delimiters, or numeric-looking parts with trailing dots (GitHub Advisory, Guzzle 7.15.2 release, Guzzle 8.0.1 release). If immediate upgrade is not possible, apply the following workaround before passing any URI to Guzzle — validate the host component using: 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.'); } — and apply the same check to any explicit Host header. Re-parse third-party UriInterface objects with new Uri((string) $uri) before validation. Note that noncanonical numeric spellings such as 127.1, 2130706433, 0x7f000001, and 0177.0.0.1 are not blocked by the patch and remain a risk for applications relying on text-based host comparisons for SSRF protection (GitHub Advisory).

Community reactions

The advisory was authored by GrahamCampbell (Guzzle maintainer) and credited reporter bilguunbicktivism for discovery. The fix was merged on July 26, 2026, with the advisory published August 3, 2026. The advisory explicitly notes that Guzzle does not offer SSRF protection and that neither cache poisoning nor cross-tenant compromise was established, tempering the severity framing. No significant broader media coverage or notable researcher commentary beyond the official advisory has been identified at this time (GitHub Advisory).

Additional resources


SourceThis report was generated using AI

Related PHP vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

CVE-2026-53599HIGH7.5
  • PHP logoPHP
  • redaxo/source
NoYesJul 31, 2026
CVE-2026-69246HIGH7.2
  • PHP logoPHP
  • guzzlehttp/guzzle
NoYesAug 03, 2026
CVE-2026-54768MEDIUM6.9
  • PHP logoPHP
  • wp-graphql/wp-graphql
NoNoJul 31, 2026
CVE-2026-69245MEDIUM6.5
  • PHP logoPHP
  • guzzlehttp/guzzle
NoYesAug 03, 2026
CVE-2026-55825LOW3.1
  • PHP logoPHP
  • composer://contao/contao
NoYesJul 31, 2026

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management