CVE-2023-50917: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: MajorDoMo (aka Major Domestic Module) before 0662e5e allows command execution via thumb.php shell metacharacters. NOTE: this is unrelated to the Majordomo mailing-list manager.
CWE description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
CVSS v3 base score: 9.8
Published: 2023-12-15T17:15Z
Repository: https://github.com/sergejey/majordomo
Language: PHP
Files changed: modules/thumb/thumb.php
Commit date: 2023-11-14 19:41:16 +0200
Commit message: Update thumb.php

Diff: diff --git a/modules/thumb/thumb.php b/modules/thumb/thumb.php
index 593b2a57..32f4704b 100644
--- a/modules/thumb/thumb.php
+++ b/modules/thumb/thumb.php
@@ -16,15 +16,23 @@ if (!defined('PATH_TO_FFMPEG')) {
 define("_I_CACHING", "0");               //    Chaching enabled, 1 - yes, 0 - no
 define("_I_CACHE_PATH", "./cms/cached/"); //    Path to cache dir
 define("_I_CACHE_EXPIRED", "2592000");   //    Expired time for images in seconds, 0 - never expired
 
 
-//$img=($_REQUEST['img']);
+$url = gr('url');
+$img = gr('img');
+$username = gr('username');
+$password = gr('password');
+$transport = gr('transport');
 
-//$img=urldecode($_REQUEST['img']);
+$w = gr('w', 'int');
+$h = gr('h', 'int');
+$live = gr('live', 'int');
 
-if (IsSet($url) && $url != '') {
+$debug_mode = gr('debug', 'int');
+
+if (isset($url) && $url != '') {
     $tmp_url = base64_decode($url);
     if (!$img) {
         $filename = 'thumb_' . md5($tmp_url) . basename(preg_replace('/\W/', '', $tmp_url));
         $img = _I_CACHE_PATH . $filename;
     }
@@ -34,27 +42,27 @@ if (IsSet($url) && $url != '') {
         $resolution = '1280x720';
         if ($w && $h) {
             $resolution = $w . 'x' . $h;
         }
         //-re -f v4l2 -video_size 1280x720 -i /dev/video0
-        $cmd = 'fswebcam -r ' . $resolution . ' ' . $img_tmp;
-        if ($_GET['debug']) {
+        $cmd = 'fswebcam -r ' . escapeshellarg($resolution) . ' ' . escapeshellarg($img_tmp);
+        if ($debug_mode) {
             echo $cmd . '<br/>';
         } else {
             exec($cmd);
         }
         if (file_exists($img_tmp)) {
             rename($img_tmp, $img);
         }
     }
 }
 
-if ($_GET['debug']) {
+if ($debug_mode) {
     $live = 0;
 }
 
-if (IsSet($url) && $url != '') {
+if (isset($url) && $url != '') {
 
     $resize = '';
     if ($w && $h) {
         $resize = ' -vf scale=' . $w . ':' . $h;
     } elseif ($w) {
@@ -66,20 +74,20 @@ if (IsSet($url) && $url != '') {
 
     if ($username || $password) {
         $url = str_replace('://', '://' . $username . ':' . $password . '@', $url);
     }
 
-    if (preg_match('/^rtsp:/is', $url) || preg_match('/\/dev/',$url)) {
+    if (preg_match('/^rtsp:/is', $url) || preg_match('/\/dev/', $url)) {
         //-rtsp_transport tcp // -rtsp_transport tcp
-        $stream_options = '-timelimit 15 -y -i "' . $url . '"' . $resize . ' -r 5 -f image2 -vframes 1'; //-ss 00:00:01.500
-        if ($_GET['debug']) {
-            $stream_options = '-v verbose ' . $stream_options;
+        $stream_options = '-timelimit 15 -y -i ' . escapeshellarg($url) . ($resize) . ' -r 5 -f image2 -vframes 1'; //-ss 00:00:01.500
+        if ($debug_mode) {
+            $stream_options = '-v verbose ' . escapeshellarg($stream_options);
         }
-        if ($_GET['transport']) {
-            $stream_options = '-rtsp_transport ' . $_GET['transport'] . ' ' . $stream_options;
+        if ($transport) {
+            $stream_options = '-rtsp_transport ' . escapeshellarg($transport) . ' ' . escapeshellarg($stream_options);
         }
-        $cmd = PATH_TO_FFMPEG . ' ' . $stream_options . ' ' . $img;
+        $cmd = PATH_TO_FFMPEG . ' ' . $stream_options . ' ' . escapeshellarg($img);
 
         if ($live) {
             $boundary = "my_mjpeg";
             header("Cache-Control: no-cache");
             header("Cache-Control: private");
@@ -103,11 +111,11 @@ if (IsSet($url) && $url != '') {
         } else {
             @unlink($img);
             $output = array();
             $res = exec($cmd . ' 2>&1', $output);
 
-            if ($_GET['debug']) {
+            if ($debug_mode) {
                 echo $cmd;
                 echo "<hr><pre>" . implode("\n", $output) . "</pre>";
                 exit;
             }
 
@@ -137,16 +145,15 @@ if (IsSet($url) && $url != '') {
         }
 
         $result = @mjpeg_grab_frame($url);
 
         if (!$result) {
-            $url = preg_replace('/\/\/(.+?)@/','//',$url);
+            $url = preg_replace('/\/\/(.+?)@/', '//', $url);
             $result = getURL($url, 0, $username, $password, false, array(CURLOPT_HTTPAUTH => CURLAUTH_ANY));
         }
 
 
-
         if ($result) {
 
             if ($live) {
                 $boundary = "my_mjpeg";
                 header('Accept-Range: bytes');
@@ -161,11 +168,11 @@ if (IsSet($url) && $url != '') {
                 @ini_set('zlib.output_compression', 0);
                 @ini_set('implicit_flush', 1);
                 for ($i = 0; $i < ob_get_level(); $i++) ob_end_flush();
                 ob_implicit_flush(1);
                 ob_end_flush();
-                $counter=0;
+                $counter = 0;
                 print "Content-type: image/jpeg\n\n";
                 while (true) {
                     $counter++;
                     $result = getURL($url, 0, $username, $password);
                     if ($result) {
@@ -188,49 +195,36 @@ if (IsSet($url) && $url != '') {
 
     }
 }
 
 
-//$img=str_replace('\\\\', '\\', $img);
-
-//echo $img;exit;
-
-$type = (IsSet($_REQUEST['t']) ? $_REQUEST['t'] : 0);
+$type = gr('t', 'int');
 
 /*
    Allowed types:
     0 - fit
     2 - exact size
 */
-//$img='./../../'.$img;
-
 if (file_exists($img)) {
-
-    $new_width = (int)$_REQUEST['w'];
-    $new_height = (int)$_REQUEST['h'];
+    $new_width = $w;
+    $new_height = $h;
 
     $cached_filename = md5($img . filemtime($img) . $new_width . $new_height) . '.jpg';
     $path_to_cache_file = _I_CACHE_PATH . substr($cached_filename, 0, 2);
     $cache = $path_to_cache_file . '/' . $cached_filename;
 
     //   Check the cache
     if (_I_CACHING == "1" && !$dc)
 
-        //$cache = _I_CACHE_PATH.md5($img.filemtime($img).$image_width.$image_height).".pic";
         if (file_exists($cache)) {
-            //  $resc=GetImageSize($cache);
-            //  list($w_o,$h_o,$t_o) = $resc;
-            //  if ($w_o == $image_width && $h_o == $image_height && $t_o == $image_format)
-            //  {
             header("Content-Type:image/jpeg");
             header("Content-Length: " . filesize($cache));
             header("Cache-Control: public"); // HTTP/1.1
             header("Expires: " . gmdate('D, d M Y H:i:s', (time() + 60 * 60 * 24 * 30)) . ' GMT'); // Date in the future (+30 days)
             header('Last-Modified: ' . gmdate('D, d M Y H:i:s', @filemtime($cache)) . ' GMT');
             readfile($cache);
             exit;
-            //   }
         }
 
     $filename = $img;
     $lst = GetImageSize($filename);
     $image_width = $lst[0];
@@ -247,66 +241,40 @@ if (file_exists($img)) {
             if (($new_height != 0) && ($new_height < $image_height)) {
                 $image_width = (int)($image_width * ($new_height / $image_height));
                 $image_height = $new_height;
             }
             break;
-
         case 1:
             $image_width = $new_width;
             $image_height = $image_height;
             break;
     }
 
 
-    // Remove old cached images
-    /*
-    if ($handle = opendir(_I_CACHE_PATH)) {
-       while (false !== ($file = readdir($handle))) {
-           if ($file != "." && $file != ".." && time() - filemtime(_I_CACHE_PATH.$file) > _I_CACHE_EXPIRED)
-             @unlink(_I_CACHE_PATH.$file);
-       }
-       closedir($handle);
-    }
-    */
-    //
-
-    //   endof check
     if ($image_format == 1) {
-//   Header("Content-Type:image/gif");
-//   readfile($filename);
-//   exit;
         $old_image = imagecreatefromgif($filename);
     } elseif ($image_format == 2) {
         $old_image = imagecreatefromjpeg($filename);
     } elseif ($image_format == 3) {
         $old_image = imagecreatefrompng($filename);
     } else {
         return;
     }
 
-// echo("$image_width x $image_height");
     $new_image = imageCreateTrueColor($image_width, $image_height);
     $white = ImageColorAllocate($new_image, 255, 255, 255);
     ImageFill($new_image, 0, 0, $white);
 
-    /*imageCopyResized*/
     imagecopyresampled($new_image, $old_image, 0, 0, 0, 0, $image_width, $image_height, imageSX($old_image), imageSY($old_image));
 
     //   Save to cache
     if (_I_CACHING == "1" && !$_REQUEST['dc']) {
-        //if (!file_exists(_I_CACHE_PATH)) {
-        // @mkdir(_I_CACHE_PATH, 0777);
-        //}
         if (!is_dir($path_to_cache_file)) {
             @mkdir($path_to_cache_file);
         }
-
         imageJpeg($new_image, $cache);
-        //@chmod($cache, '0666');
     }
-    //   Endof save
-
 
     Header("Content-type:image/jpeg");
     imageJpeg($new_image);
 
 

Vulnerable code: //$img=($_REQUEST['img']);
//$img=urldecode($_REQUEST['img']);
if (IsSet($url) && $url != '') {
        $cmd = 'fswebcam -r ' . $resolution . ' ' . $img_tmp;
        if ($_GET['debug']) {
if ($_GET['debug']) {
if (IsSet($url) && $url != '') {
    if (preg_match('/^rtsp:/is', $url) || preg_match('/\/dev/',$url)) {
        $stream_options = '-timelimit 15 -y -i "' . $url . '"' . $resize . ' -r 5 -f image2 -vframes 1'; //-ss 00:00:01.500
        if ($_GET['debug']) {
            $stream_options = '-v verbose ' . $stream_options;
        if ($_GET['transport']) {
            $stream_options = '-rtsp_transport ' . $_GET['transport'] . ' ' . $stream_options;
        $cmd = PATH_TO_FFMPEG . ' ' . $stream_options . ' ' . $img;
            if ($_GET['debug']) {
            $url = preg_replace('/\/\/(.+?)@/','//',$url);

                $counter=0;
//$img=str_replace('\\\\', '\\', $img);

//echo $img;exit;

$type = (IsSet($_REQUEST['t']) ? $_REQUEST['t'] : 0);
//$img='./../../'.$img;


    $new_width = (int)$_REQUEST['w'];
    $new_height = (int)$_REQUEST['h'];
        //$cache = _I_CACHE_PATH.md5($img.filemtime($img).$image_width.$image_height).".pic";
            //  $resc=GetImageSize($cache);
            //  list($w_o,$h_o,$t_o) = $resc;
            //  if ($w_o == $image_width && $h_o == $image_height && $t_o == $image_format)
            //  {
            //   }

    // Remove old cached images
    /*
    if ($handle = opendir(_I_CACHE_PATH)) {
       while (false !== ($file = readdir($handle))) {
           if ($file != "." && $file != ".." && time() - filemtime(_I_CACHE_PATH.$file) > _I_CACHE_EXPIRED)
             @unlink(_I_CACHE_PATH.$file);
       }
       closedir($handle);
    }
    */
    //

    //   endof check
//   Header("Content-Type:image/gif");
//   readfile($filename);
//   exit;
// echo("$image_width x $image_height");
    /*imageCopyResized*/
        //if (!file_exists(_I_CACHE_PATH)) {
        // @mkdir(_I_CACHE_PATH, 0777);
        //}

        //@chmod($cache, '0666');
    //   Endof save

Fixed code: $url = gr('url');
$img = gr('img');
$username = gr('username');
$password = gr('password');
$transport = gr('transport');
$w = gr('w', 'int');
$h = gr('h', 'int');
$live = gr('live', 'int');
$debug_mode = gr('debug', 'int');

if (isset($url) && $url != '') {
        $cmd = 'fswebcam -r ' . escapeshellarg($resolution) . ' ' . escapeshellarg($img_tmp);
        if ($debug_mode) {
if ($debug_mode) {
if (isset($url) && $url != '') {
    if (preg_match('/^rtsp:/is', $url) || preg_match('/\/dev/', $url)) {
        $stream_options = '-timelimit 15 -y -i ' . escapeshellarg($url) . ($resize) . ' -r 5 -f image2 -vframes 1'; //-ss 00:00:01.500
        if ($debug_mode) {
            $stream_options = '-v verbose ' . escapeshellarg($stream_options);
        if ($transport) {
            $stream_options = '-rtsp_transport ' . escapeshellarg($transport) . ' ' . escapeshellarg($stream_options);
        $cmd = PATH_TO_FFMPEG . ' ' . $stream_options . ' ' . escapeshellarg($img);
            if ($debug_mode) {
            $url = preg_replace('/\/\/(.+?)@/', '//', $url);
                $counter = 0;
$type = gr('t', 'int');
    $new_width = $w;
    $new_height = $h;