CVE-2022-31043 (published 2022-06-10T00:15Z)

Guzzle is an open source PHP HTTP client. In affected versions `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. 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. Users unable to upgrade may consider an alternative approach which would be to use their own redirect middleware. Alternately users may simply disable redirects all together if redirects are not expected or required.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:P/I:N/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-212
cwe_name: Improper Removal of Sensitive Information Before Storage or Transfer
description: The product stores, transfers, or shares a resource that contains sensitive information, but it does not properly remove that information before the product makes the resource available to unauthorized actors.
extended_description: 
            Resources that may contain sensitive data include documents, packets, messages, databases, etc. While this data may be useful to an individual user or small set of users who share the resource, it may need to be removed before the resource can be shared outside of the trusted group. The process of removal is sometimes called cleansing or scrubbing.
            For example, a product for editing documents might not remove sensitive data such as reviewer comments or the local pathname where the document is stored. Or, a proxy might not remove an internal IP address from headers before making an outgoing request to an Internet site.
         
         

Fix information:
repo_url: https://github.com/guzzle/guzzle
commit_details: repo_url: https://github.com/guzzle/guzzle
author: Graham Campbell
author_date: 2022-06-09 22:39:15+01:00
msg: Release 7.4.4 (#3023)
repository: repo_url: https://github.com/guzzle/guzzle
repo_name: guzzle/guzzle
description: Guzzle, an extensible PHP HTTP client
repo_language: PHP
owner: guzzle
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,11 @@
 
 Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
 
+## 7.4.4 - 2022-06-09
+
+* Fix failure to strip Authorization header on HTTP downgrade
+* Fix failure to strip the Cookie header on change in host or HTTP downgrade
+
 ## 7.4.3 - 2022-05-25
 
 * Fix cross-domain cookie leakage

programming_language: Markdown
filename: RedirectMiddleware.php
change_type: ModificationType.MODIFY
diff: @@ -142,7 +142,7 @@ class RedirectMiddleware
     }
 
     /**
-     * Check for too many redirects
+     * Check for too many redirects.
      *
      * @throws TooManyRedirectsException Too many redirects.
      */
@@ -178,7 +178,7 @@ class RedirectMiddleware
             $modify['body'] = '';
         }
 
-        $uri = $this->redirectUri($request, $response, $protocols);
+        $uri = self::redirectUri($request, $response, $protocols);
         if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
             $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion'];
             $uri = Utils::idnUriConvert($uri, $idnOptions);
@@ -198,19 +198,46 @@ class RedirectMiddleware
             $modify['remove_headers'][] = 'Referer';
         }
 
-        // Remove Authorization header if host is different.
-        if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
+        // Remove Authorization and Cookie headers if required.
+        if (self::shouldStripSensitiveHeaders($request->getUri(), $modify['uri'])) {
             $modify['remove_headers'][] = 'Authorization';
+            $modify['remove_headers'][] = 'Cookie';
         }
 
         return Psr7\Utils::modifyRequest($request, $modify);
     }
 
     /**
-     * Set the appropriate URL on the request based on the location header
+     * Determine if we should strip sensitive headers from the request.
+     *
+     * We return true if either of the following conditions are true:
+     *
+     * 1. the host is different;
+     * 2. the scheme has changed, and now is non-https.
      */
-    private function redirectUri(RequestInterface $request, ResponseInterface $response, array $protocols): UriInterface
-    {
+    private static function shouldStripSensitiveHeaders(
+        UriInterface $originalUri,
+        UriInterface $modifiedUri
+    ): bool {
+        if (\strcasecmp($originalUri->getHost(), $modifiedUri->getHost()) !== 0) {
+            return true;
+        }
+
+        if ($originalUri->getScheme() !== $modifiedUri->getScheme() && 'https' !== $modifiedUri->getScheme()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Set the appropriate URL on the request based on the location header.
+     */
+    private static function redirectUri(
+        RequestInterface $request,
+        ResponseInterface $response,
+        array $protocols
+    ): UriInterface {
         $location = Psr7\UriResolver::resolve(
             $request->getUri(),
             new Psr7\Uri($response->getHeaderLine('Location'))

programming_language: PHP
filename: RedirectMiddlewareTest.php
change_type: ModificationType.MODIFY
diff: @@ -272,18 +272,37 @@ class RedirectMiddlewareTest extends TestCase
         self::assertTrue($call);
     }
 
-    public function testRemoveAuthorizationHeaderOnRedirect()
+    public function crossOriginRedirectProvider()
+    {
+        return [
+            ['http://example.com?a=b', 'http://test.com/', false],
+            ['https://example.com?a=b', 'https://test.com/', false],
+            ['http://example.com?a=b', 'https://test.com/', false],
+            ['https://example.com?a=b', 'http://test.com/', false],
+            ['http://example.com?a=b', 'http://example.com/', true],
+            ['https://example.com?a=b', 'https://example.com/', true],
+            ['http://example.com?a=b', 'https://example.com/', true],
+            ['https://example.com?a=b', 'http://example.com/', false],
+        ];
+    }
+
+    /**
+     * @dataProvider crossOriginRedirectProvider
+     */
+    public function testHeadersTreatmentOnRedirect($originalUri, $targetUri, $shouldBePresent)
     {
         $mock = new MockHandler([
-            new Response(302, ['Location' => 'http://test.com']),
-            static function (RequestInterface $request) {
-                self::assertFalse($request->hasHeader('Authorization'));
+            new Response(302, ['Location' => $targetUri]),
+            static function (RequestInterface $request) use ($shouldBePresent) {
+                self::assertSame($shouldBePresent, $request->hasHeader('Authorization'));
+                self::assertSame($shouldBePresent, $request->hasHeader('Cookie'));
+
                 return new Response(200);
             }
         ]);
         $handler = HandlerStack::create($mock);
         $client = new Client(['handler' => $handler]);
-        $client->get('http://example.com?a=b', ['auth' => ['testuser', 'testpass']]);
+        $client->get($originalUri, ['auth' => ['testuser', 'testpass'], 'headers' => ['Cookie' => 'foo=bar']]);
     }
 
     public function testNotRemoveAuthorizationHeaderOnRedirect()

programming_language: PHP