See how guzzlephp compares to other vendors in security performance
Impact
CookieJar does not restrict a cookie scoped to an IP address to the exact host that set it. When a stored cookie's Domain attribute is an IPv4 literal (Domain=192.168.0.1), a bracketed IPv6 literal (Domain=[::1]), or a bare numeric value that denotes an IPv4 address (Domain=1, which is 0.0.0.1), SetCookie::matchesDomain() applies ordinary subdomain (suffix) matching to it instead of requiring an exact host. The IP-address check inside matchesDomain() is applied only to the request host and never to the cookie's own domain, and response cookies that carry an explicit Domain attribute are stored as suffix-matchable. As a result Guzzle sends such a cookie to any host whose name ends in that value as a trailing label, for example sending a Domain=192.168.0.1 cookie to evil.192.168.0.1, or a Domain=1 cookie to evil.1. The same flaw lets a look-alike host store a cookie that Guzzle then sends to the bare IP address. Depending on how the receiving service interprets the cookie, the impact is cross-host cookie disclosure, cookie injection, or session fixation.
You are affected if your application uses Guzzle's cookie support, for example new Client(['cookies' => true]) or an explicit CookieJar, reuses one cookie jar across more than one host or trust boundary, and that jar can hold a cookie scoped to an IP-address or bare-numeric host. To actually leak a cookie the application must also resolve and connect to a look-alike host whose name ends in the IP-address label, such as evil.192.168.0.1. Such names are not publicly registrable, so in practice an attacker needs some influence over name resolution, which is realistic on private, split-horizon, container, or development networks. You are not affected if you do not use Guzzle's cookie support, if you use a separate cookie jar per host or trust boundary, or if your jars never hold cookies scoped to IP-address or bare-numeric hosts. This issue is independent of public suffix list validation. Per RFC 6265, an IP-address cookie domain must match only the exact host that set it and must never match a subdomain.
Patches
The issue is patched in 7.12.3 and later. Starting in that release, Guzzle matches an IPv4 literal, a bracketed IPv6 literal, or a bare-numeric cookie Domain only against the exact request host, and no longer applies subdomain suffix matching to them.
Workarounds
If you cannot upgrade immediately, do not reuse one CookieJar instance across untrusted and trusted origins. Use a separate cookie jar per host or trust boundary, or disable cookie handling for requests to untrusted hosts, and avoid scoping cookies to IP-address or bare-numeric hosts. In particular, avoid new Client(['cookies' => true]) for any client that may contact unrelated hosts at different trust levels, because that option creates a single shared jar for the whole client.
References
https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3 https://datatracker.ietf.org/doc/html/rfc6265#section-5.3 https://url.spec.whatwg.org/#concept-ipv4-parser
Impact
guzzlehttp/psr7 did not reject URI host components containing authority delimiters (/, ?, #, @, or \), an embedded port such as host:8080, or IPv6 brackets that do not frame the host (such as [::1 or ::1]). Uri::assertValidHost() rejected only control characters, so these malformed hosts were accepted both when parsing a URI string — including through new Uri(), an absolute-form request target passed to Message::parseRequest(), and the synthetic SERVERNAME or SERVERADDR fallback used by ServerRequest::fromGlobals() — and when setting a host with Uri::withHost(). Because the stored host was not validated, Uri::getHost() could return a value that does not agree with the URI's authority or with the host that is actually used on the wire. For example, Uri::withHost('good.com@evil.com') is accepted, and new Uri('http://[gggg::1]/') produces a getHost() of '[gggg:' while getAuthority() returns '[gggg::1'.
You are affected if your application builds a Uri or calls Uri::withHost() with host data that is wholly or partly attacker-controlled and then relies on Uri::getHost() for a security or routing decision, such as host allowlisting or denylisting, an SSRF guard, cookie scoping, or proxy-bypass selection. You are also affected if you parse, forward, proxy, or re-serialize URIs that come from untrusted raw HTTP messages or from synthetic $SERVER values. Because getHost() can then disagree with the real connection target, the concrete impact depends on how your application and any downstream HTTP/1.x component use the URI, and may include host-based access-control bypass, server-side request forgery, routing to an unintended host, or Host header confusion. You are not affected if every host you place into a Uri is already trusted and well formed.
Applications using guzzlehttp/guzzle are affected in the same way through Guzzle's use of PSR-7. Guzzle derives the request Host header from the request URI, and its cookie matching and noproxy proxy-bypass matching both read Uri::getHost(). An application that builds a Guzzle request URI from attacker-controlled host input can therefore have its Host header, cookie scope, or proxy-bypass decision computed against a host that differs from the one Guzzle connects to. This is not the normal request-sending path, and an application that passes only trusted, well-formed URIs to Guzzle and never builds a request URI from attacker-controlled host input is not affected through Guzzle's standard client APIs.
Patches
The issue is patched in 2.12.3 and later. Starting in that release, guzzlehttp/psr7 rejects URI host components that contain authority delimiters, an embedded port, or IPv6 brackets that do not frame the host, both when parsing a URI and via Uri::withHost(), so Uri::getHost() agrees with the URI authority. A framed bracketed literal whose contents are not a valid IPv6 address is preserved rather than rejected, so getHost() still agrees with the authority.
Workarounds
If you cannot upgrade immediately, validate and normalize host values before you build or modify a Uri from untrusted input, and do not treat Uri::getHost() as a trust boundary for a host you have not independently checked. Before passing a host to Uri, reject any value that contains /, ?, #, @, or \, that contains a colon outside a bracketed IPv6 literal, or that has unbalanced brackets. Applications that parse, forward, replay, or re-serialize raw HTTP messages, or that run with attacker-controlled $SERVER values, should validate the host before calling Message::parseRequest() or ServerRequest::fromGlobals(), and should avoid reparsing untrusted input.
References
https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2 https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3 https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
Impact
CookieJar incorrectly accepts cookies with a dot-only Domain attribute, such as Domain=., Domain=.., Domain=..., and whitespace-padded variants such as Domain= . . In affected versions, SetCookie::matchesDomain() removes leading dots from the cookie domain, normalizing dot-only values to the empty string; SetCookie::validate() only rejected a strictly empty domain, so these cookies could be stored and the empty normalized domain was treated as matching any request host.
An attacker-controlled origin that an application requests with a shared cookie jar can therefore set a cookie that Guzzle later sends to unrelated hosts using the same jar. This may allow cookie injection or session fixation against downstream services, depending on how those services interpret the injected cookie. Applications are affected when they use Guzzle's cookie support, for example new Client(['cookies' => true]) or an explicit shared CookieJar, and reuse the same jar across attacker-controlled and trusted origins.
Applications that do not use Guzzle's cookie support, or that use separate cookie jars per origin or trust boundary, are not affected. This issue is distinct from public suffix list validation: dot-only domains contain no domain label and should not match unrelated hosts.
Patches
The issue is patched in 7.12.1 and later. Starting in that release, Guzzle rejects dot-only cookie Domain attributes and prevents an empty normalized cookie domain from matching any request host.
Workarounds
If you cannot upgrade immediately, do not reuse the same CookieJar instance across untrusted and trusted origins. Use separate cookie jars per origin or trust boundary, or disable cookie handling for requests to untrusted hosts.
Avoid using new Client(['cookies' => true]) for clients that may contact unrelated hosts with different trust levels, because that option creates one shared jar for the client.
Impact
guzzlehttp/psr7 did not reject CR/LF characters in certain first-party HTTP start-line fields: the request method, protocol version, and response reason phrase. If an application placed attacker-controlled data into one of those fields and later serialized the PSR-7 message as raw HTTP/1.x, for example with Message::toString() or an equivalent serializer, the serialized message could contain attacker-controlled header lines. The issue can also be reached through Message::parseRequest() or Message::parseResponse() when malformed raw messages are parsed into first-party PSR-7 objects and then serialized again.
Creating or modifying a Request, Response, or other PSR-7 object alone is not sufficient. The issue requires the malformed message to be serialized and written to the network, forwarded, replayed, or otherwise processed by software that does not independently reject the malformed start line. This is not the normal request-sending path used by guzzlehttp/guzzle; applications using guzzlehttp/psr7 only through Guzzle's standard HTTP client APIs are not expected to be affected.
Applications are most likely to be affected when they manually serialize PSR-7 messages, forward raw HTTP messages, or use custom transports, proxying, crawling, webhook delivery, testing, or similar code. Depending on how downstream HTTP/1.1 components parse the serialized message, this may lead to header injection, response splitting, request smuggling, or cache poisoning.
Patches
The issue is patched in 2.12.1 and later. Starting in that release, guzzlehttp/psr7 rejects CR/LF characters in HTTP method, protocol version, and response reason phrase values before storing them in first-party message objects.
Workarounds
If you cannot upgrade immediately, reject CR/LF in untrusted method, protocol version, and reason phrase values before constructing or modifying PSR-7 messages.
Applications that parse, forward, replay, or serialize raw HTTP messages cannot work around the parser entry points by validating only after parsing. They should validate the raw start line before calling Message::parseRequest() or Message::parseResponse(), avoid reparsing untrusted raw messages, or upgrade. If an application runs with attacker-controlled synthetic $SERVER values, validate REQUESTMETHOD and SERVERPROTOCOL before calling ServerRequest::fromGlobals().
Impact
The built-in cURL handlers (GuzzleHttp\Handler\CurlHandler and GuzzleHttp\Handler\CurlMultiHandler, used by default whenever the PHP cURL extension is available) accept an https:// proxy — a proxy reached over a TLS-encrypted connection — through the proxy request option, client-level proxy defaults, or proxy environment variables such as httpproxy, httpsproxy, HTTPSPROXY, allproxy, and ALLPROXY.
When the installed libcurl does not support HTTPS proxies, behavior depends on the libcurl version/build:
- libcurl older than 7.50.2 silently treats an https:// proxy as a plaintext http:// proxy. The TLS connection to the proxy is never established, and the proxy leg is cleartext with no error or warning. - libcurl 7.50.2 through 7.51.x rejects the unsupported proxy scheme at connect time, so no cleartext exposure occurs, but the failure is late and opaque. - libcurl 7.52.0 or newer builds without HTTPS-proxy support also fail at connect time rather than downgrading.
The security-relevant case is the silent downgrade on libcurl older than 7.50.2. An application is affected when it sends requests through one of the built-in cURL handlers, configures an https:// proxy expecting the proxy connection itself to be encrypted, and runs with libcurl older than 7.50.2.
In that configuration, traffic expected to be protected by TLS on the hop to the proxy is transmitted in cleartext. Proxy authentication credentials (the Proxy-Authorization header, proxy userinfo in the proxy URL, or CURLOPTPROXYUSERPWD) are sent without encryption, and the CONNECT target host and port for tunneled HTTPS requests are exposed. For plain HTTP requests, request headers and bodies are also exposed on the proxy leg. End-to-end HTTPS requests tunneled through the proxy remain protected by their inner TLS session; the exposure is limited to the proxy negotiation and proxy credentials.
Applications that do not configure an https:// proxy are not affected. Installations running libcurl 7.52.0 or newer built with HTTPS-proxy support are not affected because HTTPS proxies work as intended. Installations running libcurl 7.50.2 through 7.51.x, or libcurl 7.52.0 or newer built without HTTPS-proxy support, are not exposed to the silent cleartext downgrade, but Guzzle now rejects those unsupported configurations up front as well. The built-in stream handler is not affected; the issue is specific to the cURL handlers' proxy handling. Low-level cURL options under the curl request option, such as CURLOPTPROXY or CURLOPTPROXYTYPE, are advanced custom configuration and remain the caller's responsibility.
Patches
The issue is patched in 7.12.1 and later. Starting in that release, the built-in cURL handlers detect whether the installed libcurl supports HTTPS proxies — requiring both libcurl 7.52.0 or newer and the CURLVERSIONHTTPSPROXY feature bit — and reject a request configured through Guzzle's first-class proxy handling with an https:// proxy up front by throwing a GuzzleHttp\Exception\RequestException. No request bytes reach the network when the proxy cannot be used securely. Versions before 7.12.1 are affected by the silent downgrade when run against libcurl older than 7.50.2.
Workarounds
If you cannot upgrade immediately, do not configure an https:// proxy on an installation whose libcurl lacks HTTPS-proxy support, and verify the capability in application code before using one. Remember to check proxy environment variables as well as any explicit proxy option:
php $curl = \curlversion(); $httpsProxyBit = \defined('CURLVERSIONHTTPSPROXY') ? \CURLVERSIONHTTPSPROXY : (1 << 21);
if (\versioncompare($curl['version'], '7.52.0', '<') || 0 === ($curl['features'] & $httpsProxyBit)) { throw new \RuntimeException('Installed libcurl does not support HTTPS proxies.'); }
Upgrading the system libcurl to 7.52.0 or newer built with HTTPS-proxy support also resolves the underlying unsupported-proxy behavior.
Impact
guzzlehttp/psr7 did not reject ASCII control characters, whitespace, or DEL in first-party URI host components. The issue requires a PSR-7 request to be serialized into a raw HTTP/1.x message, for example with GuzzleHttp\Psr7\Message::toString() or an equivalent custom serializer. Creating a Uri, Request, or other PSR-7 object alone is not sufficient. The malformed host must be copied into the serialized Host header without further validation.
A vulnerable flow is:
1. An application accepts a user-controlled URL. 2. The URL is used to construct a PSR-7 Uri or Request. 3. The host component contains CRLF or another header-unsafe character. 4. The request is serialized into a raw HTTP/1.x message without an explicit Host header. 5. The host is copied into the serialized Host header. 6. The serialized request is written to the network or otherwise processed by software that does not independently reject the malformed host.
In that flow, an attacker can cause the serialized request to contain additional attacker-controlled header lines. For example, a host containing "\r\nX-Injected: yes" can cause the generated Host header to span multiple HTTP header lines.
This is not the normal request-sending path used by guzzlehttp/guzzle. Applications using guzzlehttp/psr7 only through Guzzle's standard HTTP client APIs are not expected to be affected. Applications are most likely to be affected when they manually serialize PSR-7 requests, forward raw HTTP messages, or use custom transports, proxying, crawling, webhook delivery, or similar request-dispatch code that serializes requests without independently validating URI hosts and header data. In deployments involving HTTP/1.1 connection reuse, proxies, gateways, or load balancers, this malformed serialized request may also contribute to request smuggling or cache poisoning, depending on how downstream components parse the request.
Patches
The issue is patched in 2.10.2 and later. 1.x is end-of-life and will not receive a patch.
Workarounds
If you cannot upgrade immediately, validate and reject all untrusted URI strings before constructing PSR-7 Uri or Request instances. Reject input containing ASCII control characters, whitespace, or DEL, including CRLF, tab, space, NUL, or DEL characters:
php if (pregmatch('/[\x00-\x20\x7F]/', $untrustedUrl)) { throw new \InvalidArgumentException('Insecure URL detected'); }
Applications that manually serialize or forward requests should also ensure the final HTTP client, transport, or serializer rejects invalid URI and header data before writing requests to the network.
References
https://www.rfc-editor.org/rfc/rfc9112.html#section-3.2 https://www.rfc-editor.org/rfc/rfc9112.html#section-5 https://www.rfc-editor.org/rfc/rfc9112.html#section-11.2 https://www.rfc-editor.org/rfc/rfc9110.html#section-7.2
Impact
guzzlehttp/psr7 improperly interpreted malformed Host header values when constructing request URIs from inbound request data. This issue concerns inbound request parsing and server request construction. It does not require serializing a PSR-7 request, and it is not part of the normal outbound request-sending path used by guzzlehttp/guzzle.
A vulnerable flow is:
1. An attacker controls a raw HTTP request or server variable containing a Host value. 2. The Host value contains URI authority delimiters, such as trusted.example@evil.example. 3. guzzlehttp/psr7 uses that value to construct a URI. 4. The URI parser treats the portion before @ as userinfo and the portion after @ as the URI host. 5. The resulting PSR-7 request URI host differs from the original Host header value.
For example, Host: trusted.example@evil.example can result in a PSR-7 URI whose host is evil.example, while the original Host header value remains trusted.example@evil.example.
Applications are affected if they parse attacker-controlled raw HTTP requests with GuzzleHttp\Psr7\Message::parseRequest() or the legacy 1.x GuzzleHttp\Psr7\parserequest() function, or if they build server requests from attacker-controlled server variables with GuzzleHttp\Psr7\ServerRequest::fromGlobals() or GuzzleHttp\Psr7\ServerRequest::getUriFromGlobals(), and then rely on the resulting URI host for routing, allow-list checks, credential selection, or forwarding decisions. Applications using guzzlehttp/psr7 only through Guzzle's standard HTTP client APIs are not expected to be affected. In affected forwarding or gateway scenarios, this may cause requests or credentials to be sent to an unintended host.
Patches
The issue is patched in 2.10.2 and later. 1.x is end-of-life and will not receive a patch.
Workarounds
If you cannot upgrade immediately, validate Host values before passing untrusted request data to Message::parseRequest(), legacy 1.x parserequest(), ServerRequest::fromGlobals(), or ServerRequest::getUriFromGlobals().
Accept only uri-host [ ":" port ]. Reject values containing whitespace, control characters, userinfo (@), path (/ or \), query (?), fragment (#), malformed IP literals or bracket syntax, or invalid port syntax.
Do not validate Host by prefixing it with http:// and passing it to parseurl(), because that can reinterpret malformed values as URI userinfo and host.
References
https://www.rfc-editor.org/rfc/rfc9112.html#section-3.2 https://www.rfc-editor.org/rfc/rfc9112.html#section-3.3 https://www.rfc-editor.org/rfc/rfc9110.html#section-4.2.4 https://www.rfc-editor.org/rfc/rfc9110.html#section-7.2
Impact
Affected versions of Laminas Diactoros accepted a single line feed (LF / \n ) character at the end of a header name. When serializing such a header name containing a line-feed into the on-the-wire representation of a HTTP/1.x message, the resulting message would be syntactically invalid, due to the header line being terminated too early. An attacker that is able to control the header names that are passed to Laminas Diactoros would be able to intentionally craft invalid messages, possibly causing application errors or invalid HTTP requests being sent out with an PSR-18 HTTP client. The latter might present a denial of service vector if a remote service’s web application firewall bans the application due to the receipt of malformed requests.
Patches
The problem has been patched in the following versions:
- 2.18.1 - 2.19.1 - 2.20.1 - 2.21.1 - 2.22.1 - 2.23.1 - 2.24.2 - 2.25.2
Workarounds
Validate HTTP header keys and/or values, and if using user-supplied values, filter them to strip off leading or trailing newline characters before calling withHeader().
References
- CVE-2023-29197 - GHSA-wxmh-65f7-jcvw
Impact
Improper header parsing. An attacker could sneak in a newline (\n) into both the header names and values. While the specification states that \r\n\r\n is used to terminate the header list, many servers in the wild will also accept \n\n.
Patches
The issue is patched in 1.9.1 and 2.4.5.
Workarounds
There are no known workarounds.
References
https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4
Change in port should be considered a change in origin
Impact
Authorization headers on requests are sensitive information. When using our Curl handler, it is possible to use the CURLOPTHTTPAUTH option to specify an Authorization header. On making a request which responds with a redirect to a URI with a different origin, if we choose to follow it, we should remove the CURLOPTHTTPAUTH and CURLOPTUSERPWD options before continuing, stopping curl from appending the Authorization header to the new request. Previously, we would only consider a change in host. Now, we consider any change in host, port or scheme to be a change in origin.
Patches
Affected Guzzle 7 users should upgrade to Guzzle 7.4.5 as soon as possible. Affected users using any earlier series of Guzzle should upgrade to Guzzle 6.5.8 or 7.4.5. Note that a partial fix was implemented in Guzzle 7.4.2, where a change in host would trigger removal of the curl-added Authorization header, however this earlier fix did not cover change in scheme or change in port.
Workarounds
If you do not require or expect redirects to be followed, one should simply disable redirects all together. Alternatively, one can specify to use the Guzzle stream handler backend, rather than curl.
References
RFC9110 Section 15.4 CVE-2022-27776
For more information
If you have any questions or comments about this advisory, please get in touch with us in #guzzle on the PHP HTTP Slack. Do not report additional security advisories in that public channel, however - please follow our vulnerability reporting process.
Impact
Cookie headers on requests are sensitive information. On making a request using the https scheme to a server which responds with a redirect to a URI with the http scheme, or on making a request to a server which responds with a redirect to a a URI to a different host, we should not forward the Cookie header on. Prior to this fix, only cookies that were managed by our cookie middleware would be safely removed, and any Cookie header manually added to the initial request would not be stripped. We now always strip it, and allow the cookie middleware to re-add any cookies that it deems should be there.
Patches
Affected Guzzle 7 users should upgrade to Guzzle 7.4.4 as soon as possible. Affected users using any earlier series of Guzzle should upgrade to Guzzle 6.5.7 or 7.4.4.
Workarounds
An alternative approach would be to use your own redirect middleware, rather than ours, if you are unable to upgrade. If you do not require or expect redirects to be followed, one should simply disable redirects all together.
References
RFC9110 Section 15.4
For more information
If you have any questions or comments about this advisory, please get in touch with us in #guzzle on the PHP HTTP Slack. Do not report additional security advisories in that public channel, however - please follow our vulnerability reporting process.
Impact
Authorization headers on requests are sensitive information. On making a request using the https scheme to a server which responds with a redirect to a URI with the http scheme, we should not forward the Authorization header on. This is much the same as to how we don't forward on the header if the host changes. Prior to this fix, https to http downgrades did not result in the Authorization header being removed, only changes to the host.
Patches
Affected Guzzle 7 users should upgrade to Guzzle 7.4.4 as soon as possible. Affected users using any earlier series of Guzzle should upgrade to Guzzle 6.5.7 or 7.4.4.
Workarounds
An alternative approach would be to use your own redirect middleware, rather than ours, if you are unable to upgrade. If you do not require or expect redirects to be followed, one should simply disable redirects all together.
References
RFC9110 Section 15.4
For more information
If you have any questions or comments about this advisory, please get in touch with us in #guzzle on the PHP HTTP Slack. Do not report additional security advisories in that public channel, however - please follow our vulnerability reporting process.
Impact
Previous version of Guzzle contain a vulnerability with the cookie middleware. The vulnerability is that it is not checked if the cookie domain equals the domain of the server which sets the cookie via the Set-Cookie header, allowing a malicious server to set cookies for unrelated domains. For example an attacker at www.example.com might set a session cookie for api.example.net, logging the Guzzle client into their account and retrieving private API requests from the security log of their account.
Note that our cookie middleware is disabled by default, so most library consumers will not be affected by this issue. Only those who manually add the cookie middleware to the handler stack or construct the client with ['cookies' => true] are affected. Moreover, those who do not use the same Guzzle client to call multiple domains and have disabled redirect forwarding are not affected by this vulnerability.
Patches
Affected Guzzle 7 users should upgrade to Guzzle 7.4.3 as soon as possible. Affected users using any earlier series of Guzzle should upgrade to Guzzle 6.5.6 or 7.4.3.
Workarounds
If you do not need support for cookies, turn off the cookie middleware. It is already off by default, but if you have turned it on and no longer need it, turn it off.
References
RFC6265 Section 5.3 RFC9110 Section 15.4
For more information
If you have any questions or comments about this advisory, please get in touch with us in #guzzle on the PHP HTTP Slack. Do not report additional security advisories in that public channel, however - please follow our vulnerability reporting process.
Impact
Improper header parsing. An attacker could sneak in a carriage return character (\r) and pass untrusted values in both the header names and values.
Patches
The issue is patched in 1.8.4 and 2.1.1.
Workarounds
There are no known workarounds.
References
https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4