CVE-2018-1000658 (published 2018-09-06T17:29Z)

LimeSurvey version prior to 3.14.4 contains a file upload vulnerability in upload functionality that can result in an attacker gaining code execution via webshell. This attack appear to be exploitable via an authenticated user uploading a zip archive which can contains malicious php files that can be called under certain circumstances. This vulnerability appears to have been fixed in after commit 91d143230eb357260a19c8424b3005deb49a47f7 / version 3.14.4.

Severity: MEDIUM
CVSS v2 base score: 6.5 (AV:N/AC:L/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-434
cwe_name: Unrestricted Upload of File with Dangerous Type
description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
extended_description: 
            
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/LimeSurvey/LimeSurvey
commit_details: repo_url: https://github.com/LimeSurvey/LimeSurvey
author: LouisGac
author_date: 2018-08-16 16:59:51+02:00
msg: [security] Fixed issue #13960: vulnerabilities in file upload functionality
repository: repo_url: https://github.com/LimeSurvey/LimeSurvey
repo_name: LimeSurvey/LimeSurvey
description: The most popular FOSS online survey tool on the web.
repo_language: JavaScript
owner: LimeSurvey
file_changes: filename: export.php
change_type: ModificationType.MODIFY
diff: @@ -348,7 +348,7 @@ class export extends Survey_Common_Action
 // Default to 2 (16 and up)
             Yii::app()->session['spssversion'] = 2;
         }
-        $spssver = Yii::app()->request->getParam('spssver', Yii::app()->session['spssversion']); 
+        $spssver = Yii::app()->request->getParam('spssver', Yii::app()->session['spssversion']);
         Yii::app()->session['spssversion'] = $spssver;
 
         $length_varlabel = '231'; // Set the max text length of Variable Labels
@@ -1150,7 +1150,7 @@ class export extends Survey_Common_Action
 
 
             $lang = Yii::app()->request->getPost('save_language');
-            $tempdir = Yii::app()->getConfig("tempdir");
+
 
             // Setting the selected language for printout
             App()->setLanguage($lang);
@@ -1169,7 +1169,9 @@ class export extends Survey_Common_Action
             //NEED TO GET QID from $quexmlpdf
             $qid = intval($quexmlpdf->getQuestionnaireId());
 
-            $zipdir = $this->_tempdir($tempdir);
+            Yii::import('application.helpers.common_helper', true);
+            $zipdir = createRandomTempDir();
+
 
             $f1 = "$zipdir/quexf_banding_{$qid}_{$lang}.xml";
             $f2 = "$zipdir/quexmlpdf_{$qid}_{$lang}.pdf";
@@ -1185,7 +1187,7 @@ class export extends Survey_Common_Action
 
 
             Yii::app()->loadLibrary('admin.pclzip');
-            $zipfile = "$tempdir/quexmlpdf_{$qid}_{$lang}.zip";
+            $zipfile = Yii::app()->getConfig("tempdir").DIRECTORY_SEPARATOR."quexmlpdf_{$qid}_{$lang}.zip";
             $z = new PclZip($zipfile);
             $z->create($zipdir, PCLZIP_OPT_REMOVE_PATH, $zipdir);
 
@@ -1222,10 +1224,12 @@ class export extends Survey_Common_Action
         $fullAssetsDir = Template::getTemplatePath($oSurvey->template);
         $aLanguages = $oSurvey->getAllLanguages();
 
-        $tempdir = Yii::app()->getConfig("tempdir");
-        $zipdir = $this->_tempdir($tempdir);
+        Yii::import('application.helpers.common_helper', true);
+        $zipdir = createRandomTempDir();
 
         $fn = "printable_survey_".preg_replace('([^\w\s\d\-_~,;\[\]\(\).])','',$oSurvey->currentLanguageSettings->surveyls_title)."_{$oSurvey->primaryKey}.zip";
+
+        $tempdir = Yii::app()->getConfig("tempdir");
         $zipfile = "$tempdir/".$fn;
 
         Yii::app()->loadLibrary('admin.pclzip');

programming_language: PHP
filename: labels.php
change_type: ModificationType.MODIFY
diff: @@ -60,7 +60,8 @@ class labels extends Survey_Common_Action
             // Create temporary directory
             // If dangerous content is unzipped
             // then no one will know the path
-            $extractdir = $this->_tempdir(Yii::app()->getConfig('tempdir'));
+            Yii::import('application.helpers.common_helper', true);
+            $extractdir = createRandomTempDir();
             $zipfilename = $_FILES['the_file']['tmp_name'];
             $basedestdir = Yii::app()->getConfig('uploaddir')."/labels";
             $destdir = $basedestdir."/$lid/";

programming_language: PHP
filename: surveyadmin.php
change_type: ModificationType.MODIFY
diff: @@ -272,7 +272,8 @@ class SurveyAdmin extends Survey_Common_Action
             // Create temporary directory
             // If dangerous content is unzipped
             // then no one will know the path
-            $extractdir  = $this->_tempdir(Yii::app()->getConfig('tempdir'));
+            Yii::import('application.helpers.common_helper', true);
+            $extractdir  = createRandomTempDir();
             $zipfilename = $_FILES['the_file']['tmp_name'];
             $basedestdir = Yii::app()->getConfig('uploaddir')."/surveys";
             $destdir     = $basedestdir."/$iSurveyID/";

programming_language: PHP
filename: Survey_Common_Action.php
change_type: ModificationType.MODIFY
diff: @@ -1234,28 +1234,6 @@ class Survey_Common_Action extends CAction
         return array($aImportedFilesInfo, $aErrorFilesInfo);
     }
 
-    /**
-     * Creates a temporary directory
-     *
-     * @access protected
-     * @param string $dir
-     * @param string $prefix
-     * @param int $mode
-     * @return string
-     */
-    protected function _tempdir($dir, $prefix = '', $mode = 0700)
-    {
-        if (substr($dir, -1) != DIRECTORY_SEPARATOR) {
-            $dir .= DIRECTORY_SEPARATOR;
-        }
-
-        do {
-            $path = $dir.$prefix.mt_rand(0, 9999999);
-        } while (!mkdir($path, $mode));
-
-        return $path;
-    }
-
     /**
      * Get extra menus from plugins that are using event beforeAdminMenuRender
      *

programming_language: PHP
filename: common_helper.php
change_type: ModificationType.MODIFY
diff: @@ -4961,6 +4961,74 @@ function isAssociativeArray($array){
     return false;
 }
 
+
+/**
+* Create a directory in tmp dir using a random string
+*
+* @param  string $dir      the temp directory (if empty will use the one from configuration)
+* @param  string $prefix   wanted prefix for the directory
+* @param  int    $mode     wanted  file mode for this directory
+* @return string           the path of the created directory
+*/
+function createRandomTempDir($dir=null, $prefix = '', $mode = 0700)
+{
+
+    $sDir = (empty($dir)) ? Yii::app()->getConfig('tempdir') : get_absolute_path ($dir);
+
+    if (substr($sDir, -1) != DIRECTORY_SEPARATOR) {
+        $sDir .= DIRECTORY_SEPARATOR;
+    }
+
+    do {
+        $sRandomString = getRandomString();
+        $path = $sDir.$prefix.$sRandomString;
+    }
+    while (!mkdir($path, $mode));
+
+    return $path;
+}
+
+/**
+ * Generate a random string, using openssl if available, else using md5
+ * @param  int    $length wanted lenght of the random string (only for openssl mode)
+ * @return string
+ */
+function getRandomString($length=32){
+
+    if ( function_exists('openssl_random_pseudo_bytes') ) {
+        $token = "";
+        $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+        $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
+        $codeAlphabet.= "0123456789";
+        for($i=0;$i<$length;$i++){
+            $token .= $codeAlphabet[crypto_rand_secure(0,strlen($codeAlphabet))];
+        }
+    }else{
+        $token = md5(uniqid(rand(), true));
+    }
+    return $token;
+}
+
+/**
+ * Get a random number between two values using openssl_random_pseudo_bytes
+ * @param  int    $min
+ * @param  int    $max
+ * @return string
+ */
+function crypto_rand_secure($min, $max) {
+        $range = $max - $min;
+        if ($range < 0) return $min; // not so random...
+        $log = log($range, 2);
+        $bytes = (int) ($log / 8) + 1; // length in bytes
+        $bits = (int) $log + 1; // length in bits
+        $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
+        do {
+            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
+            $rnd = $rnd & $filter; // discard irrelevant bits
+        } while ($rnd >= $range);
+        return $min + $rnd;
+}
+
 /**
  * Test if a given zip file is Zip Bomb
  * see comment here : http://php.net/manual/en/function.zip-entry-filesize.php

programming_language: PHP
repo_url: https://github.com/LimeSurvey/LimeSurvey
commit_details: repo_url: https://github.com/LimeSurvey/LimeSurvey
author: LouisGac
author_date: 2018-08-16 11:50:23+02:00
msg: [security] Fixed issue #13960: vulnerabilities in file upload functionality
repository: repo_url: https://github.com/LimeSurvey/LimeSurvey
repo_name: LimeSurvey/LimeSurvey
description: The most popular FOSS online survey tool on the web.
repo_language: JavaScript
owner: LimeSurvey
file_changes: filename: common_helper.php
change_type: ModificationType.MODIFY
diff: @@ -4960,3 +4960,40 @@ function isAssociativeArray($array){
     }
     return false;
 }
+
+/**
+ * Test if a given zip file is Zip Bomb
+ * see comment here : http://php.net/manual/en/function.zip-entry-filesize.php
+ * @param string $zip_filename
+ * @return int
+ */
+function isZipBomb($zip_filename)
+{
+    return ( get_zip_originalsize($zip_filename) >  getMaximumFileUploadSize() );
+}
+
+/**
+ * Get the original size of a zip archive to prevent Zip Bombing
+ * see comment here : http://php.net/manual/en/function.zip-entry-filesize.php
+ * @param string $filename
+ * @return int
+ */
+function get_zip_originalsize($filename) {
+
+    if ( function_exists ('zip_entry_filesize') ){
+        $size = 0;
+        $resource = zip_open($filename);
+        while ($dir_resource = zip_read($resource)) {
+            $size += zip_entry_filesize($dir_resource);
+        }
+        zip_close($resource);
+
+        return $size;
+    }else{
+        if ( YII_DEBUG ){
+            Yii::app()->setFlashMessage(gT("Warning! php zip extension is not installed on your server. You're not protected from Zip Bomb attaacks."), 'error');
+        }
+    }
+
+    return -1;
+}

programming_language: PHP
filename: pclzip.lib.php
change_type: ModificationType.MODIFY
diff: @@ -221,6 +221,13 @@
       die('Abort '.basename(__FILE__).' : Missing zlib extensions');
     }
 
+
+    // Added by LS Team to prevent Zip Bombing
+    if (isZipBomb($p_zipname)){
+        die('Abort '.basename(__FILE__).' : Unzipped file is superior to upload_max_filesize or to post_max_size');
+    }
+
+
     // ----- Set the attributes
     $this->zipname = $p_zipname;
     $this->zip_fd = 0;
@@ -3670,7 +3677,7 @@
     }
 
     // Added by LS Team to check for invalid paths
-    $p_entry['filename'] = get_absolute_path($p_entry['filename']);    
+    $p_entry['filename'] = get_absolute_path($p_entry['filename']);
 
     // ----- Add the path
     if ($p_path != '') {

programming_language: PHP