CVE-2020-28337: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: A directory traversal issue in the Utils/Unzip module in Microweber through 1.1.20 allows an authenticated attacker to gain remote code execution via the backup restore feature. To exploit the vulnerability, an attacker must have the credentials of an administrative user, upload a maliciously constructed ZIP file with file paths including relative paths (i.e., ../../), move this file into the backup directory, and execute a restore on this file.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 7.2
Published: 2021-02-15T20:15Z
Repository: https://github.com/microweber/microweber
Language: CSS
Files changed: src/MicroweberPackages/Utils/Zip/Unzip.php, userfiles/modules/admin/backup_v2/css/style.css
Commit date: 2020-11-04 11:43:08 +0200
Commit message: update

Diff: diff --git a/src/MicroweberPackages/Utils/Zip/Unzip.php b/src/MicroweberPackages/Utils/Zip/Unzip.php
index 73e6ac1c1..61af6f38d 100644
--- a/src/MicroweberPackages/Utils/Zip/Unzip.php
+++ b/src/MicroweberPackages/Utils/Zip/Unzip.php
@@ -80,18 +80,23 @@ class Unzip
             if (!empty($is_any)) {
                 return $is_any;
             }
 
         } else if (function_exists('gzinflate')) {
+
             if (!$files = $this->_list_files()) {
                 $this->set_error('ZIP folder was empty.');
-
                 return false;
             }
 
             $file_locations = array();
             foreach ($files as $file => $trash) {
+
+                if (strpos($file,'..') !== false) {
+                    continue;
+                }
+
                 $dirname = pathinfo($file, PATHINFO_DIRNAME);
                 $extension = (pathinfo($file, PATHINFO_EXTENSION));
 
                 $folders = explode('/', $dirname);
                 $out_dn = $this->_target_dir . '/' . $dirname;
@@ -203,16 +208,21 @@ class Unzip
             while ($entry = zip_read($archive)) {
                 $name = zip_entry_name($entry);
                 $name = dirname($name);
                 $is_dir_there = $target_dir . $name;
 
+                if (strpos($is_dir_there,'..') !== false) {
+                   continue;
+                }
+
                 if ($name != '.') {
                     $dirs_tree[] = $is_dir_there;
 
                 }
 
             }
+
             $dirs_tree = array_unique($dirs_tree);
 
             foreach ($dirs_tree as $item) {
                 try {
                     if (!is_dir($item)) {
@@ -236,10 +246,15 @@ class Unzip
 
                 while ($entry = zip_read($archive)) {
                     $size = zip_entry_filesize($entry);
                     $name = zip_entry_name($entry);
                     $target_file_to_save = normalize_path($target_dir . $name, false);
+
+                    if (strpos($target_file_to_save,'..') !== false) {
+                        continue;
+                    }
+
                     $target_file_to_save_dir = dirname($target_file_to_save);
                     if(!is_dir($target_file_to_save_dir)){
                         mkdir_recursive($target_file_to_save_dir);
                     }
 //
@@ -261,12 +276,13 @@ class Unzip
                     }
                     @fclose($unzipped);
                 }
                 zip_close($archive);
             }
-
-            return $file_locations;
+            if (!empty($file_locations)) {
+                $file_locations = array_unique($file_locations);
+            }
         }
 
         return $file_locations;
     }
 
@@ -567,10 +583,18 @@ class Unzip
      *
      * @return Unziped file.
      */
     private function _extract_file($compressed_file_name, $target_file_name = false, $underscore_case = false)
     {
+        if (strpos($target_file_name,'..') !== false) {
+            return false;
+        }
+
+        if (strpos($compressed_file_name,'..') !== false) {
+            return false;
+        }
+
         if (!sizeof($this->compressed_list)) {
             $this->set_debug('Trying to unzip before loading file list... Loading it!');
             $this->_list_files(false, $compressed_file_name);
         }
 
diff --git a/userfiles/modules/admin/backup_v2/css/style.css b/userfiles/modules/admin/backup_v2/css/style.css
index 6ad58b40d..614ae19c5 100644
--- a/userfiles/modules/admin/backup_v2/css/style.css
+++ b/userfiles/modules/admin/backup_v2/css/style.css
@@ -138,10 +138,11 @@
     padding-bottom: 15px;
     font-family: monospace;
     border-radius: 4px;
     margin-top: 15px;
     margin-bottom: 15px;
+    overflow-y: scroll;
 }
 
 .js-export-log:empty {
     display: none;
 }

Vulnerable code: 

            return $file_locations;
Fixed code: 

                if (strpos($file,'..') !== false) {
                    continue;
                }

                if (strpos($is_dir_there,'..') !== false) {
                   continue;
                }



                    if (strpos($target_file_to_save,'..') !== false) {
                        continue;
                    }

            if (!empty($file_locations)) {
                $file_locations = array_unique($file_locations);
            }
        if (strpos($target_file_name,'..') !== false) {
            return false;
        }

        if (strpos($compressed_file_name,'..') !== false) {
            return false;
        }

    overflow-y: scroll;