Summary
A host validation bypass in the Guzzle PHP HTTP client allows libcurl and stream transports to connect to a different host than the one the application validated, due to inconsistencies in URI parsing and percent-decoding.
When applications build request URIs from untrusted input and rely on pre-Guzzle host checks, an attacker can craft special host spellings to reach excluded endpoints and read their responses.
Affected context
This vulnerability affects PHP applications that use Guzzle as an HTTP client and:
- Construct request URIs from user-controlled or otherwise untrusted input
- Perform host-based validation (e.g., IP literal checks, allowlists, blocklists) before handing the URI to Guzzle
- Rely on Guzzle transports (cURL handlers or StreamHandler) to honor the same host spelling used in validation
It is most relevant in scenarios where Guzzle is used to fetch URLs on behalf of a user, perform webhook callbacks, or integrate with internal services, and host decisions are made on the textual URI rather than the final resolved authority.
Exploit preconditions
- Attack surface: Application endpoints that accept or derive URLs, domains, or hosts from untrusted input and then fetch them via Guzzle.
- Required access: Ability to influence the request URI (query parameters, JSON bodies, headers, configuration fields, or upstream data sources).
- Trigger point: The moment Guzzle constructs and sends an HTTP request, passing the URI string into libcurl or fopen() while separately supplying a Host header.
- Why exploitation is possible: Guzzle validates and reasons about the host using the literal URI string, while libcurl applies percent-decoding and IDNA mapping to derive the actual authority used for connection and TLS decisions. The divergence between application-level checks and transport-level parsing allows a crafted host spelling that fails validation but is still resolved and connected to, bypassing the intended host restrictions and related logic such as proxy selection and header stripping.
Why this happens
The core issue arises from inconsistent interpretation of URI authority between the application layer and the underlying HTTP transport.
Guzzle gives libcurl and stream handlers the request URI as written and supplies the Host header separately. Application code may run checks over the literal host string (for example, using filter_var() to reject IP literals or applying allowlists), assuming this host is the one actually contacted.
However, libcurl independently parses and normalizes the authority, performing percent-decoding and IDNA mapping. This means that hosts such as 127.0.0.%31 may be rejected by application-level filters yet decoded to 127.0.0.1 by libcurl and used to connect to loopback.
This behavioral mismatch maps to multiple CWEs:
- CWE-918 (SSRF): The client can be induced to connect to internal or otherwise restricted hosts.
- CWE-180 (Incorrect String Comparison): Host checks operate over the raw string, not the normalized value used by the transport.
- CWE-436 (Interpretation Conflict): Different components interpret the same data differently, leading to security decisions based on inconsistent views.
- CWE-941 (Incomplete Validation): Input validation fails to account for alternate encodings like percent-escaping and IDNA mapping.
Abstractly, the bug class is "security decision made on one representation of data while enforcement and execution use another representation," enabling attackers to bypass host-based controls without altering the apparent validation logic.
Potential impact
- An attacker may cause Guzzle to connect to loopback (127.0.0.1) or other internal endpoints that the application attempted to exclude.
- An attacker may read responses from services that should have been unreachable, exposing internal APIs, metadata endpoints, or administrative interfaces.
- An attacker could influence proxy routing decisions, causing traffic to bypass expected proxies or security appliances.
- Authorization or cookie-stripping logic may be misapplied, potentially preserving sensitive headers on redirects where they should have been removed.
- Host-based access control policies may be undermined, weakening assumptions about what external resources the application can safely contact.
General mitigation guidance
Effective mitigation focuses on aligning host validation with the actual authority used by the transport and reducing trust in user-provided host data.
Recommended defensive patterns include:
- Normalize and canonicalize hostnames and IP addresses before performing security checks, including decoding percent-encoded characters and applying appropriate IDNA processing where needed.
- Centralize URL and host validation in a dedicated component that understands the same parsing rules as the HTTP transport, avoiding ad hoc checks scattered across the codebase.
- Enforce strict allowlists for outbound destinations when performing server-side HTTP requests based on untrusted input, preferring explicit lists of safe domains or network ranges rather than negative filters.
- Limit SSRF risk by constraining which schemes, ports, and address families can be used (for example, disallow loopback, link-local, and private addresses where not strictly required).
- Separate user-visible hostnames from internal routing targets, so that external URLs are mapped to preconfigured internal endpoints instead of being used directly as connection parameters.
- Apply least-privilege principles to outbound connectors, ensuring that even if host-based checks are bypassed, the client's network access is constrained by infrastructure-level controls such as firewalls or egress policies.
How teams detect issues like this
Detection typically involves analyzing both application-level URL handling and how Guzzle interacts with its transports.
Useful approaches include:
- Static analysis rules that flag patterns where URLs from untrusted sources are passed directly into HTTP clients without robust normalization and allowlist checks, particularly when functions like
filter_var()are used for IP validation without considering encoding tricks. - Code review signals such as:
- Security decisions based on string comparisons of hosts or IPs.
- Use of separate Host header manipulation alongside URI construction.
- Logic that depends on host spelling for decisions like proxy routing or header stripping.
- Configuration and policy checks that ensure outbound HTTP clients are constrained by network egress rules, preventing unrestricted access to internal ranges.
- Runtime monitoring for suspicious outbound requests, such as traffic to loopback or private IP ranges triggered by endpoints that accept URLs or domains from users.
- Adoption of automated security tooling or SAST platforms that understand SSRF patterns and can detect interpretation conflicts between application code and underlying libraries.

