CVE-2024-29023: Insufficient Information (NVD-CWE-noinfo)
value: Xibo is an Open Source Digital Signage platform with a web content management system and Windows display player software. Session tokens are exposed in the return of session search API call on the sessions page. Subsequently they can be exfiltrated and used to hijack a session. Users must be granted access to the session page, or be a super admin. Users should upgrade to version 3.3.10 or 4.0.9 which fix this issue. Customers who host their CMS with the Xibo Signage service have already received an upgrade or patch to resolve this issue regardless of the CMS version that they are running. Patches are available for earlier versions of Xibo CMS that are out of security support: 2.3 patch ebeccd000b51f00b9a25f56a2f252d6812ebf850.diff. 1.8 patch a81044e6ccdd92cc967e34c125bd8162432e51bc.diff. There are no known workarounds for this vulnerability.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-12T21:15Z
Repository: https://github.com/dasgarner/xibo-cms
Language: PHP
Files changed: lib/Controller/Sessions.php, lib/Factory/SessionFactory.php, lib/Helper/Session.php, lib/Xmds/Soap.php
Commit date: 2024-03-18 08:52:12 +0000
Commit message: Sessions: Add sanitization in getIp() and disable output of session ID in session grid.

Diff: diff --git a/lib/Controller/Sessions.php b/lib/Controller/Sessions.php
index 2077860a..cd7d2e50 100644
--- a/lib/Controller/Sessions.php
+++ b/lib/Controller/Sessions.php
@@ -86,15 +86,15 @@ class Sessions extends Base
             if (!$this->isApi() && $this->getUser()->isSuperAdmin()) {
 
                 $row->includeProperty('buttons');
 
                 // Edit
-                $row->buttons[] = array(
+                /*$row->buttons[] = array(
                     'id' => 'sessions_button_logout',
                     'url' => $this->urlFor('sessions.confirm.logout.form', ['id' => $row->sessionId]),
                     'text' => __('Logout')
-                );
+                );*/
             }
         }
 
         $this->getState()->template = 'grid';
         $this->getState()->recordsTotal = $this->sessionFactory->countLast();
diff --git a/lib/Factory/SessionFactory.php b/lib/Factory/SessionFactory.php
index 96bb38ec..abc22c5b 100644
--- a/lib/Factory/SessionFactory.php
+++ b/lib/Factory/SessionFactory.php
@@ -148,11 +148,18 @@ class SessionFactory extends BaseFactory
         $sql = $select . $body . $order . $limit;
 
 
 
         foreach ($this->getStore()->select($sql, $params) as $row) {
-            $entries[] = $this->createEmpty()->hydrate($row, ['stringProperties' => ['sessionId']]);
+            $session = $this->createEmpty()->hydrate($row, [
+                'stringProperties' => ['sessionId'],
+                'intProperties' => ['isExpired']
+            ]);
+            $session->userAgent = htmlspecialchars($session->userAgent);
+            $session->remoteAddress = filter_var($session->remoteAddress, FILTER_VALIDATE_IP);
+            $session->excludeProperty('sessionId');
+            $entries[] = $session;
         }
 
         // Paging
         if ($limit != '' && count($entries) > 0) {
             $results = $this->getStore()->select('SELECT COUNT(*) AS total ' . $body, $params);
diff --git a/lib/Helper/Session.php b/lib/Helper/Session.php
index b977b824..4632eeca 100644
--- a/lib/Helper/Session.php
+++ b/lib/Helper/Session.php
@@ -168,11 +168,11 @@ class Session implements \SessionHandlerInterface
         //$this->log->debug('Session read');
 
         $data = '';
         $this->key = $key;
 
-        $userAgent = substr($_SERVER['HTTP_USER_AGENT'], 0, 253);
+        $userAgent = substr(htmlspecialchars($_SERVER['HTTP_USER_AGENT']), 0, 253);
 
         try {
             $dbh = $this->getDb();
 
             // Start a transaction
@@ -418,11 +418,11 @@ class Session implements \SessionHandlerInterface
             'session_data' => $data,
             'session_expiration' => $expiry,
             'lastAccessed' => date('Y-m-d H:i:s', $lastAccessed),
             'userId' => $this->userId,
             'expired' => ($this->expired) ? 1 : 0,
-            'useragent' => substr($_SERVER['HTTP_USER_AGENT'], 0, 253),
+            'useragent' => substr(htmlspecialchars($_SERVER['HTTP_USER_AGENT']), 0, 253),
             'remoteaddr' => $this->getIp()
         ];
 
         $this->getDb()->update($sql, $params);
     }
@@ -467,11 +467,11 @@ class Session implements \SessionHandlerInterface
     private function getIp()
     {
         $clientIp = '';
         $keys = array('X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'REMOTE_ADDR');
         foreach ($keys as $key) {
-            if (isset($_SERVER[$key])) {
+            if (isset($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP) !== false) {
                 $clientIp = $_SERVER[$key];
                 break;
             }
         }
         return $clientIp;
diff --git a/lib/Xmds/Soap.php b/lib/Xmds/Soap.php
index 6e351fd4..b4716570 100644
--- a/lib/Xmds/Soap.php
+++ b/lib/Xmds/Soap.php
@@ -1770,11 +1770,11 @@ class Soap
     {
         $clientIp = '';
 
         $keys = array('X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'REMOTE_ADDR');
         foreach ($keys as $key) {
-            if (isset($_SERVER[$key])) {
+            if (isset($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP) !== false) {
                 $clientIp = $_SERVER[$key];
                 break;
             }
         }
 

Vulnerable code:                 $row->buttons[] = array(
                );
            $entries[] = $this->createEmpty()->hydrate($row, ['stringProperties' => ['sessionId']]);
        $userAgent = substr($_SERVER['HTTP_USER_AGENT'], 0, 253);
            'useragent' => substr($_SERVER['HTTP_USER_AGENT'], 0, 253),
            if (isset($_SERVER[$key])) {
            if (isset($_SERVER[$key])) {
Fixed code:                 /*$row->buttons[] = array(
                );*/
            $session = $this->createEmpty()->hydrate($row, [
                'stringProperties' => ['sessionId'],
                'intProperties' => ['isExpired']
            ]);
            $session->userAgent = htmlspecialchars($session->userAgent);
            $session->remoteAddress = filter_var($session->remoteAddress, FILTER_VALIDATE_IP);
            $session->excludeProperty('sessionId');
            $entries[] = $session;
        $userAgent = substr(htmlspecialchars($_SERVER['HTTP_USER_AGENT']), 0, 253);
            'useragent' => substr(htmlspecialchars($_SERVER['HTTP_USER_AGENT']), 0, 253),
            if (isset($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP) !== false) {
            if (isset($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP) !== false) {