CVE-2023-5866: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute (CWE-614)
value: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute in GitHub repository thorsten/phpmyfaq prior to 3.2.1.
CWE description: The Secure attribute for sensitive cookies in HTTPS sessions is not set, which could cause the user agent to send those cookies in plaintext over an HTTP session.
CVSS v3 base score: 5.7
Published: 2023-10-31T01:15Z
Repository: https://github.com/thorsten/phpmyfaq
Language: PHP
Files changed: phpmyfaq/src/phpMyFAQ/Session.php, phpmyfaq/src/phpMyFAQ/Session/Token.php
Commit date: 2023-09-09 18:43:58 +0200
Commit message: fix: improved check on secure flag for cookies

Diff: diff --git a/phpmyfaq/src/phpMyFAQ/Session.php b/phpmyfaq/src/phpMyFAQ/Session.php
index 835158b76c3..c197aeed36b 100644
--- a/phpmyfaq/src/phpMyFAQ/Session.php
+++ b/phpmyfaq/src/phpMyFAQ/Session.php
@@ -395,14 +395,11 @@ class Session
      * @param int|string|null $sessionId Session ID
      * @param int             $timeout Cookie timeout
      */
     public function setCookie(string $name, int|string|null $sessionId, int $timeout = 3600): bool
     {
-        $secure = false;
-        if (isset($_SERVER['HTTPS']) && strtoupper((string) $_SERVER['HTTPS']) === 'ON') {
-            $secure = true;
-        }
+        $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] === 443;
 
         return setcookie(
             $name,
             $sessionId ?? '',
             [
diff --git a/phpmyfaq/src/phpMyFAQ/Session/Token.php b/phpmyfaq/src/phpMyFAQ/Session/Token.php
index b2baa61c2f9..35dc75e8864 100644
--- a/phpmyfaq/src/phpMyFAQ/Session/Token.php
+++ b/phpmyfaq/src/phpMyFAQ/Session/Token.php
@@ -16,10 +16,12 @@
  */
 
 namespace phpMyFAQ\Session;
 
 use Exception;
+use phpMyFAQ\Configuration;
+use phpMyFAQ\System;
 
 class Token
 {
     final public const PMF_SESSION_NAME = 'pmf-csrf-token';
     private const PMF_SESSION_EXPIRY = 1800;
@@ -184,15 +186,31 @@ class Token
             ->setPage($page)
             ->setExpiry(time() + $expiry)
             ->setSessionToken(md5(base64_encode(random_bytes(32))))
             ->setCookieToken(md5(base64_encode(random_bytes(32))));
 
-        setcookie($token->getCookieName($page), $token->getCookieToken(), ['expires' => $token->getExpiry()]);
+        setcookie(
+            $token->getCookieName($page),
+            $token->getCookieToken(),
+            [
+                'expires' => $token->getExpiry(),
+                'path' => dirname((string) $_SERVER['SCRIPT_NAME']),
+                'domain' => parse_url(Configuration::getConfigurationInstance()->getDefaultUrl(), PHP_URL_HOST),
+                'samesite' => 'strict',
+                'secure' => $this->isSecure(),
+                'httponly' => true,
+            ]
+        );
 
         return $_SESSION[self::PMF_SESSION_NAME][$page] = $token;
     }
 
     private function getCookieName(string $page): string
     {
         return sprintf('%s-%s', self::PMF_SESSION_NAME, substr(md5($page), 0, 10));
     }
+
+    private function isSecure(): bool
+    {
+        return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] === 443;
+    }
 }

Vulnerable code:         $secure = false;
        if (isset($_SERVER['HTTPS']) && strtoupper((string) $_SERVER['HTTPS']) === 'ON') {
            $secure = true;
        }
        setcookie($token->getCookieName($page), $token->getCookieToken(), ['expires' => $token->getExpiry()]);
Fixed code:         $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] === 443;
use phpMyFAQ\Configuration;
use phpMyFAQ\System;
        setcookie(
            $token->getCookieName($page),
            $token->getCookieToken(),
            [
                'expires' => $token->getExpiry(),
                'path' => dirname((string) $_SERVER['SCRIPT_NAME']),
                'domain' => parse_url(Configuration::getConfigurationInstance()->getDefaultUrl(), PHP_URL_HOST),
                'samesite' => 'strict',
                'secure' => $this->isSecure(),
                'httponly' => true,
            ]
        );

    private function isSecure(): bool
    {
        return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] === 443;
    }