CVE-2022-25321: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: An issue was discovered in Cerebrate through 1.4. XSS could occur in the bookmarks component.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2022-02-18T06:15Z
Repository: https://github.com/cerebrate-project/cerebrate
Language: PHP
Files changed: src/Model/Table/UserSettingsTable.php, templates/Instance/home.php, templates/element/layouts/sidebar/bookmark-entry.php
Commit date: 2022-02-07 10:48:55 +0100
Commit message: fix: [userSettings] Perform URI validation for bookmarks

- As reported by Dawid Czarnecki from Zigrin Security

Diff: diff --git a/src/Model/Table/UserSettingsTable.php b/src/Model/Table/UserSettingsTable.php
index bdfe535e..cc4b5dbe 100644
--- a/src/Model/Table/UserSettingsTable.php
+++ b/src/Model/Table/UserSettingsTable.php
@@ -133,6 +133,20 @@ class UserSettingsTable extends AppTable
             $bookmarksData = json_encode($bookmarksData);
             $result = $this->editSetting($user, $this->BOOKMARK_SETTING_NAME, $bookmarksData);
         }
         return $result;
     }
+
+    /**
+     * validURI - Ensure the provided URI can be safely put as a link
+     *
+     * @param String $uri
+     * @return bool if the URI is safe to be put as a link
+     */
+    public function validURI(String $uri): bool
+    {
+        $parsed = parse_url($uri);
+        $isLocalPath = empty($parsed['scheme']) && empty($parsed['domain']) && !empty($parsed['path']);
+        $isValidURL = !empty($parsed['scheme']) && in_array($parsed['scheme'], ['http', 'https']) && filter_var($uri, FILTER_SANITIZE_URL);
+        return $isLocalPath || $isValidURL;
+    }
 }
diff --git a/templates/Instance/home.php b/templates/Instance/home.php
index 6d912661..e5803c27 100644
--- a/templates/Instance/home.php
+++ b/templates/Instance/home.php
@@ -1,28 +1,38 @@
 <?php
+
+use Cake\ORM\TableRegistry;
+
 $bookmarks = !empty($loggedUser->user_settings_by_name['ui.bookmarks']['value']) ? json_decode($loggedUser->user_settings_by_name['ui.bookmarks']['value'], true) : [];
+$this->userSettingsTable = TableRegistry::getTableLocator()->get('UserSettings');
 ?>
 
 <h3>
     <?= $this->Bootstrap->icon('bookmark', [
         'class' => ['fa-fw']
     ]); ?>
     <?= __('Bookmarks') ?>
 </h3>
 <div class="row">
-    <?php if (!empty($bookmarks)): ?>
+    <?php if (!empty($bookmarks)) : ?>
         <ul class="col-sm-12 col-md-10 col-l-8 col-xl-8 mb-3">
             <?php foreach ($bookmarks as $bookmark) : ?>
                 <li class="list-group-item">
-                    <a href="<?= h($bookmark['url']) ?>" class="w-bold">
-                        <?= h($bookmark['label']) ?>
-                    </a>
+                    <?php if ($this->userSettingsTable->validURI($bookmark['url'])): ?>
+                        <a href="<?= h($bookmark['url']) ?>" class="w-bold">
+                            <?= h($bookmark['label']) ?>
+                        </a>
+                    <?php else: ?>
+                        <span class="w-bold">
+                            <?= h($bookmark['url']) ?>
+                        </span>
+                    <?php endif; ?>
                     <span class="ms-3 fw-light"><?= h($bookmark['name']) ?></span>
                 </li>
             <?php endforeach; ?>
         </ul>
-    <?php else: ?>
+    <?php else : ?>
         <p class="fw-light"><?= __('No bookmarks') ?></p>
     <?php endif; ?>
 </div>
 
 <h3>
diff --git a/templates/element/layouts/sidebar/bookmark-entry.php b/templates/element/layouts/sidebar/bookmark-entry.php
index f2d01c1b..a33ffe43 100644
--- a/templates/element/layouts/sidebar/bookmark-entry.php
+++ b/templates/element/layouts/sidebar/bookmark-entry.php
@@ -1,7 +1,10 @@
 <?php
     use Cake\Routing\Router;
+    use Cake\ORM\TableRegistry;
+
+    $this->userSettingsTable = TableRegistry::getTableLocator()->get('UserSettings');
 
     $seed = 'sb-' . mt_rand();
     $icon = $entry['icon'] ?? '';
     $label = $entry['label'] ?? '';
     $name = $entry['name'] ?? '';
@@ -12,19 +15,21 @@
     $currentURL = Router::url(null);
     if ($url == $currentURL) {
         $active = true;
     }
 
+    $validURI = $this->userSettingsTable->validURI($url);
+
     echo $this->Bootstrap->button([
         'nodeType' => 'a',
         'text' => h($label),
         'title' => h($name),
         'variant' => 'dark',
         'outline' => !$active,
         'size' => 'sm',
         'icon' => h($icon),
-        'class' => ['mb-1'],
+        'class' => ['mb-1', !$validURI ? 'disabled' : ''],
         'params' => [
-            'href' => h($url),
+            'href' => $validURI ? h($url) : '#',
         ]
     ]);
 ?>

Vulnerable code:     <?php if (!empty($bookmarks)): ?>
                    <a href="<?= h($bookmark['url']) ?>" class="w-bold">
                        <?= h($bookmark['label']) ?>
                    </a>
    <?php else: ?>
        'class' => ['mb-1'],
            'href' => h($url),
Fixed code: 
    /**
     * validURI - Ensure the provided URI can be safely put as a link
     *
     * @param String $uri
     * @return bool if the URI is safe to be put as a link
     */
    public function validURI(String $uri): bool
    {
        $parsed = parse_url($uri);
        $isLocalPath = empty($parsed['scheme']) && empty($parsed['domain']) && !empty($parsed['path']);
        $isValidURL = !empty($parsed['scheme']) && in_array($parsed['scheme'], ['http', 'https']) && filter_var($uri, FILTER_SANITIZE_URL);
        return $isLocalPath || $isValidURL;
    }

use Cake\ORM\TableRegistry;

$this->userSettingsTable = TableRegistry::getTableLocator()->get('UserSettings');
    <?php if (!empty($bookmarks)) : ?>
                    <?php if ($this->userSettingsTable->validURI($bookmark['url'])): ?>
                        <a href="<?= h($bookmark['url']) ?>" class="w-bold">
                            <?= h($bookmark['label']) ?>
                        </a>
                    <?php else: ?>
                        <span class="w-bold">
                            <?= h($bookmark['url']) ?>
                        </span>
                    <?php endif; ?>
    <?php else : ?>
    use Cake\ORM\TableRegistry;

    $this->userSettingsTable = TableRegistry::getTableLocator()->get('UserSettings');
    $validURI = $this->userSettingsTable->validURI($url);

        'class' => ['mb-1', !$validURI ? 'disabled' : ''],
            'href' => $validURI ? h($url) : '#',