CVE-2021-3818: Reliance on Cookies without Validation and Integrity Checking (CWE-565)
value: grav is vulnerable to Reliance on Cookies without Validation and Integrity Checking
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: 2021-09-27T13:15Z
Repository: https://github.com/getgrav/grav
Language: PHP
Files changed: CHANGELOG.md, system/src/Grav/Common/Session.php, system/src/Grav/Framework/Session/Session.php
Commit date: 2021-09-14 18:28:07 +0300
Commit message: Fixed `Session::setFlashCookieObject()` to use the same options as the main session cookie

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 221b5c1bb..f5ec741df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,10 +11,11 @@
 3. [](#bugfix)
     * Fixed escaping in PageIndex::getLevelListing()
     * Fixed validation of `number` type [#3433](https://github.com/getgrav/grav/issues/3433)
     * Fixed excessive `security.yaml` file creation [#3432](https://github.com/getgrav/grav/issues/3432)
     * Fixed incorrect port :0 with nginx unix socket setup [#3439](https://github.com/getgrav/grav/issues/3439)
+    * Fixed `Session::setFlashCookieObject()` to use the same options as the main session cookie
 
 # v1.7.20
 ## 09/01/2021
 
 2. [](#improved)
diff --git a/system/src/Grav/Common/Session.php b/system/src/Grav/Common/Session.php
index 8856e7a0a..2dbc3073a 100644
--- a/system/src/Grav/Common/Session.php
+++ b/system/src/Grav/Common/Session.php
@@ -10,10 +10,11 @@
 namespace Grav\Common;
 
 use Grav\Common\Form\FormFlash;
 use Grav\Events\SessionStartEvent;
 use Grav\Plugin\Form\Forms;
+use JsonException;
 use function is_string;
 
 /**
  * Class Session
  * @package Grav\Common
@@ -146,30 +147,33 @@ class Session extends \Grav\Framework\Session\Session
      *
      * @param string $name
      * @param mixed $object
      * @param int $time
      * @return $this
+     * @throws JsonException
      */
     public function setFlashCookieObject($name, $object, $time = 60)
     {
-        setcookie($name, json_encode($object), time() + $time, '/');
+        setcookie($name, json_encode($object, JSON_THROW_ON_ERROR), $this->getCookieOptions($time));
 
         return $this;
     }
 
     /**
      * Return object and remove it from the cookie.
      *
      * @param string $name
      * @return mixed|null
+     * @throws JsonException
      */
     public function getFlashCookieObject($name)
     {
         if (isset($_COOKIE[$name])) {
-            $object = json_decode($_COOKIE[$name], false);
-            setcookie($name, '', time() - 3600, '/');
-            return $object;
+            $cookie = $_COOKIE[$name];
+            setcookie($name, '', $this->getCookieOptions(-42000));
+
+            return json_decode($cookie, false, 512, JSON_THROW_ON_ERROR);
         }
 
         return null;
     }
 
diff --git a/system/src/Grav/Framework/Session/Session.php b/system/src/Grav/Framework/Session/Session.php
index ddab08d75..3feae5a2a 100644
--- a/system/src/Grav/Framework/Session/Session.php
+++ b/system/src/Grav/Framework/Session/Session.php
@@ -336,27 +336,16 @@ class Session implements SessionInterface
      */
     public function invalidate()
     {
         $name = $this->getName();
         if (null !== $name) {
-            $params = session_get_cookie_params();
-
-            $cookie_options = array (
-                'expires'  => time() - 42000,
-                'path'     => $params['path'],
-                'domain'   => $params['domain'],
-                'secure'   => $params['secure'],
-                'httponly' => $params['httponly'],
-                'samesite' => $params['samesite']
-            );
-
             $this->removeCookie();
 
             setcookie(
                 session_name(),
                 '',
-                $cookie_options
+                $this->getCookieOptions(-42000)
             );
         }
 
         if ($this->isSessionStarted()) {
             session_unset();
@@ -461,31 +450,40 @@ class Session implements SessionInterface
     protected function onSessionStart(): void
     {
     }
 
     /**
-     * @return void
+     * Store something in cookie temporarily.
+     *
+     * @param int|null $lifetime
+     * @return array
      */
-    protected function setCookie(): void
+    public function getCookieOptions(int $lifetime = null): array
     {
         $params = session_get_cookie_params();
 
-        $cookie_options = array (
-            'expires'  => time() + $params['lifetime'],
+        return [
+            'expires'  => time() + ($lifetime ?? $params['lifetime']),
             'path'     => $params['path'],
             'domain'   => $params['domain'],
             'secure'   => $params['secure'],
             'httponly' => $params['httponly'],
             'samesite' => $params['samesite']
-        );
+        ];
+    }
 
+    /**
+     * @return void
+     */
+    protected function setCookie(): void
+    {
         $this->removeCookie();
 
         setcookie(
             session_name(),
             session_id(),
-            $cookie_options
+            $this->getCookieOptions()
         );
     }
 
     protected function removeCookie(): void
     {

Vulnerable code:         setcookie($name, json_encode($object), time() + $time, '/');
            $object = json_decode($_COOKIE[$name], false);
            setcookie($name, '', time() - 3600, '/');
            return $object;
            $params = session_get_cookie_params();

            $cookie_options = array (
                'expires'  => time() - 42000,
                'path'     => $params['path'],
                'domain'   => $params['domain'],
                'secure'   => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite']
            );

                $cookie_options
     * @return void
    protected function setCookie(): void
        $cookie_options = array (
            'expires'  => time() + $params['lifetime'],
        );
            $cookie_options
Fixed code:     * Fixed `Session::setFlashCookieObject()` to use the same options as the main session cookie
use JsonException;
     * @throws JsonException
        setcookie($name, json_encode($object, JSON_THROW_ON_ERROR), $this->getCookieOptions($time));
     * @throws JsonException
            $cookie = $_COOKIE[$name];
            setcookie($name, '', $this->getCookieOptions(-42000));

            return json_decode($cookie, false, 512, JSON_THROW_ON_ERROR);
                $this->getCookieOptions(-42000)
     * Store something in cookie temporarily.
     *
     * @param int|null $lifetime
     * @return array
    public function getCookieOptions(int $lifetime = null): array
        return [
            'expires'  => time() + ($lifetime ?? $params['lifetime']),
        ];
    }
    /**
     * @return void
     */
    protected function setCookie(): void
    {
            $this->getCookieOptions()