diff --git a/web/ajax/modals/filterdebug.php b/web/ajax/modals/filterdebug.php index ec8022a10..f0dd63a1e 100644 --- a/web/ajax/modals/filterdebug.php +++ b/web/ajax/modals/filterdebug.php @@ -16,7 +16,7 @@ if ($fid) { $filter = new ZM\Filter($fid); if (!$filter->Id()) { - echo '
Filter not found for id '.$_REQUEST['fid'].'
'; + echo '
Filter not found for id '.$fid.'
'; } } else { $filter = new ZM\Filter(); diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index e302ca5cc..cf81de629 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -396,7 +396,7 @@ class EventsController extends AppController { } $matches = NULL; $value = preg_replace('/^\s?interval\s?/i', '', $value); - if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($value), $matches) !== 1) { + if (preg_match('/^(?P[ \-.:0-9]+)\s+(?P[_a-z]+)$/i', trim($value), $matches) !== 1) { throw new Exception('Invalid interval: ' . $value); } $expr = trim($matches['expr']); @@ -429,7 +429,7 @@ class EventsController extends AppController { $matches = NULL; // https://dev.mysql.com/doc/refman/5.5/en/expressions.html#temporal-intervals // Examples: `'1-1' YEAR_MONTH`, `'-1 10' DAY_HOUR`, `'1.999999' SECOND_MICROSECOND` - if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($interval), $matches) !== 1) { + if (preg_match('/^(?P[ \-.:0-9]+)\s+(?P[_a-z]+)$/i', trim($interval), $matches) !== 1) { throw new Exception('Invalid interval: ' . $interval); } $expr = trim($matches['expr']); diff --git a/web/api/app/Controller/HostController.php b/web/api/app/Controller/HostController.php index 58653e8d5..7e469f810 100644 --- a/web/api/app/Controller/HostController.php +++ b/web/api/app/Controller/HostController.php @@ -44,7 +44,7 @@ class HostController extends AppController { return; } # To try to prevent abuse here, we are only going to allow certain characters in the daemon and args. - $safe_daemon = preg_replace('/[^A-Za-z0-9\- \.]/', '', $daemon, -1, $count); + $safe_daemon = preg_replace('/[^A-Za-z0-9\-\.]/', '', $daemon, -1, $count); if ($count) Error("Invalid characters found in daemon string ($daemon). Potential attack?"); $safe_command = preg_replace('/[^a-z]/', '', $command, -1, $count); if ($count) Error("Invalid characters found in command string ($command). Potential attack?"); @@ -240,8 +240,8 @@ class HostController extends AppController { if ( $mid ) { // Get disk usage for $mid - ZM\Debug("Executing du -s0 $zm_dir_events/$mid | awk '{print $1}'"); - $usage = shell_exec("du -s0 $zm_dir_events/$mid | awk '{print $1}'"); + ZM\Debug("Executing du -s0 $zm_dir_events/$mid | awk '{print \$1}'"); + $usage = shell_exec("du -s0 ".escapeshellarg($zm_dir_events.'/'.$mid)." | awk '{print \$1}'"); } else { $monitors = $this->Monitor->find('all', array( 'fields' => array('Id', 'Name', 'WebColour') diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 90935fb0f..660fd8c70 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -259,7 +259,7 @@ class MonitorsController extends AppController { global $user; $mToken = $this->request->query('token') ? $this->request->query('token') : $this->request->data('token');; if ($mToken) { - $auth = ' -T '.$mToken; + $auth = ' -T '.escapeshellarg($mToken); } else if (ZM_AUTH_RELAY == 'hashed') { $auth = ' -A '.calculateAuthHash(''); # Can't do REMOTE_IP because zmu doesn't normally have access to it. } else if (ZM_AUTH_RELAY == 'plain') { @@ -278,13 +278,17 @@ class MonitorsController extends AppController { $password = $_SESSION['password']; } - $auth = ' -U ' .$user->Username().' -P '.$password; + $auth = ' -U '.escapeshellarg($user->Username()).' -P '.escapeshellarg($password); } else if (ZM_AUTH_RELAY == 'none') { - $auth = ' -U ' .$user->Username(); + $auth = ' -U '.escapeshellarg($user->Username()); } } - - $shellcmd = escapeshellcmd(ZM_PATH_BIN."/zmu $verbose -m$id $q $auth"); + + $shellcmd = ZM_PATH_BIN.'/zmu' + .($verbose ? " $verbose" : '') + .' -m'.escapeshellarg($id) + ." $q" + .$auth; $status = exec($shellcmd, $output, $rc); ZM\Debug("Command: $shellcmd output: ".implode(PHP_EOL, $output)." rc: $rc"); if ($rc) { diff --git a/web/includes/Event.php b/web/includes/Event.php index 3379b88d1..7b154f705 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -700,22 +700,20 @@ class Event extends ZM_Object { } function createVideo($format, $rate, $scale, $transform, $overwrite=false) { - $command = ZM_PATH_BIN.'/zmvideo.pl -e '.$this->{'Id'}.' -f '.$format.' -r '.sprintf('%.2F', ($rate/RATE_BASE)); - if (preg_match('/\d+x\d+/', $scale)) { - $command .= ' -S '.$scale; + $command = ZM_PATH_BIN.'/zmvideo.pl -e '.escapeshellarg($this->{'Id'}) + .' -f '.escapeshellarg(preg_replace('/[^\w]/', '', $format)) + .' -r '.escapeshellarg(sprintf('%.2F', ($rate/RATE_BASE))); + if (preg_match('/^\d+x\d+$/', $scale)) { + $command .= ' -S '.escapeshellarg($scale); } else { - if ( version_compare(phpversion(), '4.3.10', '>=') ) - $command .= ' -s '.sprintf('%.2F', ($scale/SCALE_BASE)); - else - $command .= ' -s '.sprintf('%.2f', ($scale/SCALE_BASE)); + $command .= ' -s '.escapeshellarg(sprintf('%.2F', ($scale/SCALE_BASE))); } if ($transform != '') { $transform = preg_replace('/[^\w=]/', '', $transform); - $command .= ' -t '.$transform; + $command .= ' -t '.escapeshellarg($transform); } if ($overwrite) $command .= ' -o'; - $command = escapeshellcmd($command); $result = exec($command, $output, $status); Debug("generating Video $command: result($result outptu:(".implode("\n", $output )." status($status"); return $status ? '' : rtrim($result); diff --git a/web/includes/FilterTerm.php b/web/includes/FilterTerm.php index c06beb3b2..2465969cf 100644 --- a/web/includes/FilterTerm.php +++ b/web/includes/FilterTerm.php @@ -40,6 +40,12 @@ class FilterTerm { $this->attr = preg_replace('/[^A-Za-z0-9\.]/', '', $this->attr, -1, $count); if ($count) Error("Invalid characters removed from filter attr {$term['attr']}, possible hacking attempt."); $this->op = isset($term['op']) ? $term['op'] : '='; + $valid_ops = array('=', '!=', '>=', '<=', '>', '<', 'LIKE', 'NOT LIKE', '=~', '!~', + '=[]', '![]', 'IN', 'NOT IN', 'EXISTS', 'IS', 'IS NOT'); + if (!in_array($this->op, $valid_ops)) { + Warning('Invalid operator in filter term: ' . $this->op); + $this->op = '='; + } $this->val = isset($term['val']) ? $term['val'] : ''; if (is_array($this->val)) $this->val = implode(',', $this->val); if ( isset($term['cnj']) ) { @@ -71,7 +77,7 @@ class FilterTerm { } $this->cookie = isset($term['cookie']) ? $term['cookie'] : ''; $this->placeholder = isset($term['placeholder']) ? $term['placeholder'] : null; - $this->collate = isset($term['collate']) ? $term['collate'] : ''; + $this->collate = isset($term['collate']) ? preg_replace('/[^a-zA-Z0-9_]/', '', $term['collate']) : ''; $this->multiple = isset($term['multiple']) ? $term['multiple'] : ''; $this->chosen = isset($term['chosen']) ? $term['chosen'] : ''; @@ -80,6 +86,21 @@ class FilterTerm { } } # end function __construct + private function compare($left, $op, $right) { + $right = floatval($right); + switch ($op) { + case '=': return $left == $right; + case '!=': return $left != $right; + case '>': return $left > $right; + case '>=': return $left >= $right; + case '<': return $left < $right; + case '<=': return $left <= $right; + default: + Warning("Invalid operator '$op' in compare"); + return false; + } + } + # Returns an array of values. AS term->value can be a list, we will break it apart, remove quotes etc public function sql_values() { $values = array(); @@ -433,16 +454,9 @@ class FilterTerm { } } # end foreach Storage Area } else if ( $this->attr == 'SystemLoad' ) { - $string_to_eval = 'return getLoad() '.$this->op.' '.$this->val.';'; - try { - $ret = eval($string_to_eval); - Debug("Evaled $string_to_eval = $ret"); - if ( $ret ) - return true; - } catch ( Throwable $t ) { - Error('Failed evaluating '.$string_to_eval); - return false; - } + $ret = $this->compare(getLoad(), $this->op, $this->val); + Debug("SystemLoad compare: getLoad() {$this->op} {$this->val} = " . ($ret ? 'true' : 'false')); + if ($ret) return true; } else { Error('testing unsupported pre term ' . $this->attr); } @@ -459,27 +473,13 @@ class FilterTerm { return !file_exists($event->Path()); } } else if ( $this->attr == 'DiskPercent' ) { - $string_to_eval = 'return $event->Storage()->disk_usage_percent() '.$this->op.' '.$this->val.';'; - try { - $ret = eval($string_to_eval); - Debug("Evalled $string_to_eval = $ret"); - if ( $ret ) - return true; - } catch ( Throwable $t ) { - Error('Failed evaluating '.$string_to_eval); - return false; - } + $ret = $this->compare($event->Storage()->disk_usage_percent(), $this->op, $this->val); + Debug("DiskPercent compare: " . ($ret ? 'true' : 'false')); + if ($ret) return true; } else if ( $this->attr == 'DiskBlocks' ) { - $string_to_eval = 'return $event->Storage()->disk_usage_blocks() '.$this->op.' '.$this->val.';'; - try { - $ret = eval($string_to_eval); - Debug("Evalled $string_to_eval = $ret"); - if ( $ret ) - return true; - } catch ( Throwable $t ) { - Error('Failed evaluating '.$string_to_eval); - return false; - } + $ret = $this->compare($event->Storage()->disk_usage_blocks(), $this->op, $this->val); + Debug("DiskBlocks compare: " . ($ret ? 'true' : 'false')); + if ($ret) return true; } else if ( $this->attr == 'Tags' ) { // Debug('TODO: Complete this post_sql_condition for Tags val: ' . $this->val . ' op: ' . $this->op . ' id: ' . $this->id); // Debug(print_r($this, true)); diff --git a/web/skins/classic/views/onvifprobe.php b/web/skins/classic/views/onvifprobe.php index 88132a644..d152bb7eb 100644 --- a/web/skins/classic/views/onvifprobe.php +++ b/web/skins/classic/views/onvifprobe.php @@ -50,7 +50,7 @@ function execONVIF($cmd) { function probeCameras($localIp) { $cameras = array(); - $lines = @execONVIF('probe 1.1,1.2'.(isset($_REQUEST['interface']) ? ' '.$_REQUEST['interface'] : '' )); + $lines = @execONVIF('probe 1.1,1.2'.(isset($_REQUEST['interface']) ? ' '.escapeshellarg($_REQUEST['interface']) : '' )); if ($lines) { foreach ($lines as $line) { $line = rtrim($line); @@ -93,7 +93,7 @@ function probeCameras($localIp) { function probeProfiles($device_ep, $soapversion, $username, $password) { $profiles = array(); - if ($lines = @execONVIF("profiles $device_ep $soapversion $username $password")) { + if ($lines = @execONVIF('profiles '.escapeshellarg($device_ep).' '.escapeshellarg($soapversion).' '.escapeshellarg($username).' '.escapeshellarg($password))) { foreach ($lines as $line) { $line = rtrim( $line ); diff --git a/web/views/image.php b/web/views/image.php index 9d2c34f85..8b61c3aae 100644 --- a/web/views/image.php +++ b/web/views/image.php @@ -60,6 +60,11 @@ if (!empty($_REQUEST['proxy'])) { } $url_parts = parse_url($url); + if (!$url_parts || !isset($url_parts['scheme']) || + !in_array(strtolower($url_parts['scheme']), array('http', 'https'))) { + ZM\Warning('Image proxy only supports http/https URLs'); + return; + } $username = $url_parts['user']; $password = isset($url_parts['pass']) ? $url_parts['pass'] : '';