CVE-2024-29022: 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. In affected versions some request headers are not correctly sanitised when stored in the session and display tables. These headers can be used to inject a malicious script into the session page to exfiltrate session IDs and User Agents. These session IDs / User Agents can subsequently be used to hijack active sessions. A malicious script can be injected into the display grid to exfiltrate information related to displays. 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. Upgrading to a fixed version is necessary to remediate. 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 issue.
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) {