CVE-2022-36032: Reliance on Cookies without Validation and Integrity Checking (CWE-565)
value: ReactPHP HTTP is a streaming HTTP client and server implementation for ReactPHP. In ReactPHP's HTTP server component versions starting with 0.7.0 and prior to 1.7.0, when ReactPHP is processing incoming HTTP cookie values, the cookie names are url-decoded. This may lead to cookies with prefixes like `__Host-` and `__Secure-` confused with cookies that decode to such prefix, thus leading to an attacker being able to forge cookie which is supposed to be secure. This issue is fixed in ReactPHP HTTP version 1.7.0. As a workaround, Infrastructure or DevOps can place a reverse proxy in front of the ReactPHP HTTP server to filter out any unexpected `Cookie` request headers.
CWE description: The product relies on the existence or values of cookies when performing security-critical operations, but it does not properly ensure that the setting is valid for the associated user.
CVSS v3 base score: 5.3
Published: 2022-09-06T19:15Z
Repository: https://github.com/reactphp/http
Language: PHP
Files changed: README.md, examples/55-server-cookie-handling.php, src/Message/ServerRequest.php, tests/Message/ServerRequestTest.php
Commit date: 2022-08-20 13:11:57 +0200
Commit message: Do not decode cookie names anymore

Diff: diff --git a/README.md b/README.md
index ba8d833..1070349 100644
--- a/README.md
+++ b/README.md
@@ -1302,11 +1302,11 @@ Allowed).
 The `getCookieParams(): string[]` method can be used to
 get all cookies sent with the current request.
 
 ```php 
 $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
-    $key = 'react\php';
+    $key = 'greeting';
 
     if (isset($request->getCookieParams()[$key])) {
         $body = "Your cookie value is: " . $request->getCookieParams()[$key] . "\n";
 
         return React\Http\Message\Response::plaintext(
@@ -1314,11 +1314,11 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf
         );
     }
 
     return React\Http\Message\Response::plaintext(
         "Your cookie has been set.\n"
-    )->withHeader('Set-Cookie', urlencode($key) . '=' . urlencode('test;more'));
+    )->withHeader('Set-Cookie', $key . '=' . urlencode('Hello world!'));
 });
 ```
 
 The above example will try to set a cookie on first access and
 will try to print the cookie value on all subsequent tries.
diff --git a/examples/55-server-cookie-handling.php b/examples/55-server-cookie-handling.php
index 796da24..b5e6886 100644
--- a/examples/55-server-cookie-handling.php
+++ b/examples/55-server-cookie-handling.php
@@ -1,11 +1,11 @@
 <?php
 
 require __DIR__ . '/../vendor/autoload.php';
 
 $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
-    $key = 'react\php';
+    $key = 'greeting';
 
     if (isset($request->getCookieParams()[$key])) {
         $body = "Your cookie value is: " . $request->getCookieParams()[$key] . "\n";
 
         return React\Http\Message\Response::plaintext(
@@ -13,11 +13,11 @@ $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterf
         );
     }
 
     return React\Http\Message\Response::plaintext(
         "Your cookie has been set.\n"
-    )->withHeader('Set-Cookie', urlencode($key) . '=' . urlencode('test;more'));
+    )->withHeader('Set-Cookie', $key . '=' . urlencode('Hello world!'));
 });
 
 $socket = new React\Socket\SocketServer(isset($argv[1]) ? $argv[1] : '0.0.0.0:0');
 $http->listen($socket);
 
diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php
index f446f24..fdb3ec5 100644
--- a/src/Message/ServerRequest.php
+++ b/src/Message/ServerRequest.php
@@ -184,11 +184,11 @@ final class ServerRequest extends Request implements ServerRequestInterface
         foreach ($cookieArray as $pair) {
             $pair = \trim($pair);
             $nameValuePair = \explode('=', $pair, 2);
 
             if (\count($nameValuePair) === 2) {
-                $key = \urldecode($nameValuePair[0]);
+                $key = $nameValuePair[0];
                 $value = \urldecode($nameValuePair[1]);
                 $result[$key] = $value;
             }
         }
 
diff --git a/tests/Message/ServerRequestTest.php b/tests/Message/ServerRequestTest.php
index 37cc187..a5919f6 100644
--- a/tests/Message/ServerRequestTest.php
+++ b/tests/Message/ServerRequestTest.php
@@ -249,11 +249,11 @@ class ServerRequestTest extends TestCase
             'http://localhost',
             array('Cookie' => 'react%3Bphp=is%20great')
         );
 
         $cookies = $this->request->getCookieParams();
-        $this->assertEquals(array('react;php' => 'is great'), $cookies);
+        $this->assertEquals(array('react%3Bphp' => 'is great'), $cookies);
     }
 
     public function testCookieWithoutSpaceAfterSeparatorWillBeAccepted()
     {
         $this->request = new ServerRequest(

Vulnerable code:     $key = 'react\php';
    )->withHeader('Set-Cookie', urlencode($key) . '=' . urlencode('test;more'));
    $key = 'react\php';
    )->withHeader('Set-Cookie', urlencode($key) . '=' . urlencode('test;more'));
                $key = \urldecode($nameValuePair[0]);
        $this->assertEquals(array('react;php' => 'is great'), $cookies);
Fixed code:     $key = 'greeting';
    )->withHeader('Set-Cookie', $key . '=' . urlencode('Hello world!'));
    $key = 'greeting';
    )->withHeader('Set-Cookie', $key . '=' . urlencode('Hello world!'));
                $key = $nameValuePair[0];
        $this->assertEquals(array('react%3Bphp' => 'is great'), $cookies);