CVE-2023-4422: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository cockpit-hq/cockpit prior to 2.6.3.
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: 4.8
Published: 2023-08-18T19:15Z
Repository: https://github.com/cockpit-hq/cockpit
Language: PHP
Files changed: modules/Assets/bootstrap.php
Commit date: 2023-08-06 23:24:10 +0200
Commit message: make assets uploading more secure

Diff: diff --git a/modules/Assets/bootstrap.php b/modules/Assets/bootstrap.php
index a42cbb938..f451e11d3 100644
--- a/modules/Assets/bootstrap.php
+++ b/modules/Assets/bootstrap.php
@@ -56,33 +56,39 @@ $this->module('assets')->extend([
             $files = $this->app->request->files[$param];
         } elseif (is_array($param) && isset($param['name'], $param['error'], $param['tmp_name'])) {
             $files = $param;
         }
 
+        $finfo      = finfo_open(FILEINFO_MIME_TYPE);
         $uploaded  = [];
         $failed    = [];
-        $_files    = [];
+        $_files     = [];
         $assets    = [];
 
         $allowed   = $this->app->retrieve('assets/allowed_uploads', '*');
         $allowed   = $allowed == '*' ? true : str_replace([' ', ','], ['', '|'], preg_quote(is_array($allowed) ? implode(',', $allowed) : $allowed));
         $max_size  = $this->app->retrieve('assets/max_upload_size', 0);
 
-        $forbidden = ['php', 'phar', 'phtml', 'phps', 'htm', 'html', 'htaccess'];
+        $forbiddenExtension = ['php', 'phar', 'phtml', 'phps', 'htm', 'html', 'htaccess'];
+        $forbiddenMime = ['application/x-httpd-php', 'text/html'];
 
         if (isset($files['name']) && is_array($files['name'])) {
 
             $cnt = count($files['name']);
 
             for ($i = 0; $i < $cnt; $i++) {
 
                 $_file  = $this->app->path('#tmp:').'/'.$files['name'][$i];
+                $_mime = finfo_file($finfo, $_file);
                 $_isAllowed = $allowed === true ? true : preg_match("/\.({$allowed})$/i", $_file);
                 $_sizeAllowed = $max_size ? filesize($files['tmp_name'][$i]) < $max_size : true;
 
-                // prevent uploading php files
-                if ($_isAllowed && in_array(strtolower(pathinfo($_file, PATHINFO_EXTENSION)), $forbidden)) {
+                // prevent uploading php / html files
+                if ($_isAllowed && (
+                    in_array(strtolower(pathinfo($_file, PATHINFO_EXTENSION)), $forbiddenExtension) ||
+                    in_array(strtolower($_mime), $forbiddenMime)
+                )) {
                     $_isAllowed = false;
                 }
 
                 if (!$files['error'][$i] && $_isAllowed && $_sizeAllowed && move_uploaded_file($files['tmp_name'][$i], $_file)) {
 

Vulnerable code:         $_files    = [];
        $forbidden = ['php', 'phar', 'phtml', 'phps', 'htm', 'html', 'htaccess'];
                // prevent uploading php files
                if ($_isAllowed && in_array(strtolower(pathinfo($_file, PATHINFO_EXTENSION)), $forbidden)) {
Fixed code:         $finfo      = finfo_open(FILEINFO_MIME_TYPE);
        $_files     = [];
        $forbiddenExtension = ['php', 'phar', 'phtml', 'phps', 'htm', 'html', 'htaccess'];
        $forbiddenMime = ['application/x-httpd-php', 'text/html'];
                $_mime = finfo_file($finfo, $_file);
                // prevent uploading php / html files
                if ($_isAllowed && (
                    in_array(strtolower(pathinfo($_file, PATHINFO_EXTENSION)), $forbiddenExtension) ||
                    in_array(strtolower($_mime), $forbiddenMime)
                )) {