CVE-2018-1000658 (PHP, published 2018-09-06T17:29Z)
Description: value: 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 v3 base score: 8.8)
Weakness: CWE-434 \u2014 Unrestricted Upload of File with Dangerous Type
The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
Repository: https://github.com/LimeSurvey/LimeSurvey
Files changed:
application/controllers/admin/export.php
application/controllers/admin/labels.php
application/controllers/admin/surveyadmin.php
application/core/Survey_Common_Action.php
application/helpers/common_helper.php
Commit message: [security] Fixed issue #13960: vulnerabilities in file upload functionality



Vulnerable code:
        $spssver = Yii::app()->request->getParam('spssver', Yii::app()->session['spssversion']); 
            $tempdir = Yii::app()->getConfig("tempdir");
            $zipdir = $this->_tempdir($tempdir);
            $zipfile = "$tempdir/quexmlpdf_{$qid}_{$lang}.zip";
        $tempdir = Yii::app()->getConfig("tempdir");
        $zipdir = $this->_tempdir($tempdir);
            $extractdir = $this->_tempdir(Yii::app()->getConfig('tempdir'));
            $extractdir  = $this->_tempdir(Yii::app()->getConfig('tempdir'));
    /**
     * 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;
    }


Fixed code:
        $spssver = Yii::app()->request->getParam('spssver', Yii::app()->session['spssversion']);

            Yii::import('application.helpers.common_helper', true);
            $zipdir = createRandomTempDir();

            $zipfile = Yii::app()->getConfig("tempdir").DIRECTORY_SEPARATOR."quexmlpdf_{$qid}_{$lang}.zip";
        Yii::import('application.helpers.common_helper', true);
        $zipdir = createRandomTempDir();

        $tempdir = Yii::app()->getConfig("tempdir");
            Yii::import('application.helpers.common_helper', true);
            $extractdir = createRandomTempDir();
            Yii::import('application.helpers.common_helper', true);
            $extractdir  = createRandomTempDir();

/**
* 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;
}