From 2b60a7a979567cb8c4513553f6b78909b4046bf1 Mon Sep 17 00:00:00 2001
From: Marius Burkard <m.burkard@pixcept.de>
Date: Sat, 27 Jul 2019 22:57:40 +0200
Subject: [PATCH] WIP: change system exec calls to safe variant

---
 interface/lib/app.inc.php                     |  2 +-
 interface/lib/classes/functions.inc.php       |  4 +-
 interface/lib/classes/system.inc.php          | 43 +++++++++-
 interface/lib/classes/validate_dkim.inc.php   |  5 +-
 interface/web/mail/ajax_get_json.php          | 10 ++-
 server/lib/app.inc.php                        | 16 ++++
 server/lib/classes/aps_installer.inc.php      | 19 +++--
 .../cron.d/100-monitor_email_quota.inc.php    |  5 +-
 server/lib/classes/cron.d/150-awstats.inc.php | 16 ++--
 .../lib/classes/cron.d/150-webalizer.inc.php  | 14 ++--
 .../lib/classes/cron.d/200-logfiles.inc.php   | 26 +++---
 server/lib/classes/cron.d/500-backup.inc.php  | 33 +++++---
 .../classes/cron.d/500-backup_mail.inc.php    | 30 ++++---
 .../cron.d/600-purge_mailboxes.inc.php        |  2 +-
 .../classes/cron.d/900-letsencrypt.inc.php    |  2 +-
 server/lib/classes/functions.inc.php          |  4 +-
 server/lib/classes/letsencrypt.inc.php        |  6 +-
 server/lib/classes/monitor_tools.inc.php      |  3 +-
 server/lib/classes/system.inc.php             | 82 +++++++++++++------
 .../remoteaction_core_module.inc.php          | 19 +++--
 .../plugins-available/apache2_plugin.inc.php  | 29 ++++---
 .../apps_vhost_plugin.inc.php                 |  6 +-
 22 files changed, 246 insertions(+), 130 deletions(-)

diff --git a/interface/lib/app.inc.php b/interface/lib/app.inc.php
index b02ae8526d..46f7213240 100755
--- a/interface/lib/app.inc.php
+++ b/interface/lib/app.inc.php
@@ -78,7 +78,7 @@ class app {
 		
 		$this->uses($prop);
 		if(property_exists($this, $prop)) return $this->{$prop};
-		else return null;
+		else trigger_error('Undefined property ' . $name . ' of class app', E_USER_WARNING);
 	}
 	
 	public function __destruct() {
diff --git a/interface/lib/classes/functions.inc.php b/interface/lib/classes/functions.inc.php
index 28ab9ce384..03e331f0f1 100644
--- a/interface/lib/classes/functions.inc.php
+++ b/interface/lib/classes/functions.inc.php
@@ -451,9 +451,9 @@ class functions {
 		if(file_exists($id_rsa_file)) unset($id_rsa_file);
 		if(file_exists($id_rsa_pub_file)) unset($id_rsa_pub_file);
 		if(!file_exists($id_rsa_file) && !file_exists($id_rsa_pub_file)) {
-			exec('ssh-keygen -t rsa -C '.$username.'-rsa-key-'.time().' -f '.$id_rsa_file.' -N ""');
+			$app->system->exec_safe('ssh-keygen -t rsa -C ? -f ? -N ""', $username.'-rsa-key-'.time(), $id_rsa_file);
 			$app->db->query("UPDATE client SET created_at = UNIX_TIMESTAMP(), id_rsa = ?, ssh_rsa = ? WHERE client_id = ?", @file_get_contents($id_rsa_file), @file_get_contents($id_rsa_pub_file), $client_id);
-			exec('rm -f '.$id_rsa_file.' '.$id_rsa_pub_file);
+			$app->system->exec_safe('rm -f ? ?', $id_rsa_file, $id_rsa_pub_file);
 		} else {
 			$app->log("Failed to create SSH keypair for ".$username, LOGLEVEL_WARN);
 		}
diff --git a/interface/lib/classes/system.inc.php b/interface/lib/classes/system.inc.php
index cef9424a75..d4d9cccefe 100644
--- a/interface/lib/classes/system.inc.php
+++ b/interface/lib/classes/system.inc.php
@@ -31,6 +31,8 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 class system {
 
 	var $client_service = null;
+	private $_last_exec_out = null;
+	private $_last_exec_retcode = null;
 
 	public function has_service($userid, $service) {
 		global $app;
@@ -52,8 +54,43 @@ class system {
 			return false;
 		}
 	}
-} //* End Class
-
-?>
 
+	public function last_exec_out() {
+		return $this->_last_exec_out;
+	}
+	
+	public function last_exec_retcode() {
+		return $this->_last_exec_retcode;
+	}
+	
+	public function exec_safe($cmd) {
+		$arg_count = func_num_args();
+		if($arg_count > 1) {
+			$args = func_get_args();
 
+			$pos = 0;
+			$a = 0;
+			foreach($args as $value) {
+				$a++;
+				
+				$pos = strpos($cmd, '?', $pos);
+				if($pos === false) {
+					break;
+				}
+				$value = escapeshellarg($value);
+				$cmd = substr_replace($cmd, $value, $pos, 1);
+				$pos += strlen($value);
+			}
+		}
+		
+		$this->_last_exec_out = null;
+		$this->_last_exec_retcode = null;
+		return exec($cmd, $this->_last_exec_out, $this->_last_exec_retcode);
+	}
+	
+	public function system_safe($cmd) {
+		call_user_func_array(array($this, 'exec_safe'), func_get_args());
+		return implode("\n", $this->_last_exec_out);
+	}	
+	
+} //* End Class
diff --git a/interface/lib/classes/validate_dkim.inc.php b/interface/lib/classes/validate_dkim.inc.php
index 443fe76d7f..3fbc28a0a1 100644
--- a/interface/lib/classes/validate_dkim.inc.php
+++ b/interface/lib/classes/validate_dkim.inc.php
@@ -49,10 +49,13 @@ class validate_dkim {
 	 * Validator function for private DKIM-Key
 	 */
 	function check_private_key($field_name, $field_value, $validator) {
+		global $app;
+		
 		$dkim_enabled=$_POST['dkim'];
 		if ($dkim_enabled == 'y') {
 			if (empty($field_value)) return $this->get_error($validator['errmsg']);
-			exec('echo '.escapeshellarg($field_value).'|openssl rsa -check', $output, $result);
+			$app->system->exec_safe('echo ?|openssl rsa -check', $field_value);
+			$result = $app->system->last_exec_retcode();
 			if($result != 0) return $this->get_error($validator['errmsg']);
 		}
 	}
diff --git a/interface/web/mail/ajax_get_json.php b/interface/web/mail/ajax_get_json.php
index 17fd4cf45a..69705ba6f7 100644
--- a/interface/web/mail/ajax_get_json.php
+++ b/interface/web/mail/ajax_get_json.php
@@ -54,8 +54,8 @@ if($type == 'create_dkim' && $domain_id != ''){
 	if ($dkim_strength=='') $dkim_strength = 2048;
 	
 	$rnd_val = $dkim_strength * 10;
-	exec('openssl rand -out ../../temp/random-data.bin '.$rnd_val.' 2> /dev/null', $output, $result);
-	exec('openssl genrsa -rand ../../temp/random-data.bin '.$dkim_strength.' 2> /dev/null', $privkey, $result);
+	$app->system->exec_safe('openssl rand -out ../../temp/random-data.bin '.$rnd_val.' 2> /dev/null', $output, $result);
+	$app->system->exec_safe('openssl genrsa -rand ../../temp/random-data.bin '.$dkim_strength.' 2> /dev/null', $privkey, $result);
 	unlink("../../temp/random-data.bin");
 	$dkim_private='';
 	foreach($privkey as $values) $dkim_private=$dkim_private.$values."\n";
@@ -79,12 +79,14 @@ if($type == 'create_dkim' && $domain_id != ''){
 			$selector = 'invalid domain or selector';
 		}
 		unset($dkim_public);
-		exec('echo '.escapeshellarg($dkim_private).'|openssl rsa -pubout -outform PEM 2> /dev/null',$pubkey,$result);
+		$app->system->exec_safe('echo ?|openssl rsa -pubout -outform PEM 2> /dev/null', $dkim_private);
+		$pubkey = $app->system->last_exec_out();
 		foreach($pubkey as $values) $dkim_public=$dkim_public.$values."\n";
 		$selector = $dkim_selector;
 	} else {
 		unset($dkim_public);
-		exec('echo '.escapeshellarg($dkim_private).'|openssl rsa -pubout -outform PEM 2> /dev/null',$pubkey,$result);
+		$app->system->exec_safe('echo ?|openssl rsa -pubout -outform PEM 2> /dev/null', $dkim_private);
+		$pubkey = $app->system->last_exec_out();
 		foreach($pubkey as $values) $dkim_public=$dkim_public.$values."\n";
 		$selector = $dkim_selector;
 	}
diff --git a/server/lib/app.inc.php b/server/lib/app.inc.php
index 86df2a86f6..146f2465c0 100644
--- a/server/lib/app.inc.php
+++ b/server/lib/app.inc.php
@@ -69,6 +69,22 @@ class app {
 
 	}
 
+	public function __get($name) {
+		$valid_names = array('functions', 'getconf', 'letsencrypt', 'modules', 'plugins', 'services', 'system');
+		if(!in_array($name, $valid_names)) {
+			trigger_error('Undefined property ' . $name . ' of class app', E_USER_WARNING);
+		}
+		if(property_exists($this, $name)) {
+			return $this->{$name};
+		}
+		$this->uses($name);
+		if(property_exists($this, $name)) {
+			return $this->{$name};
+		} else {
+			trigger_error('Undefined property ' . $name . ' of class app', E_USER_WARNING);
+		}
+	}
+	
 	function setCaller($caller) {
 		$this->_calling_script = $caller;
 	}
diff --git a/server/lib/classes/aps_installer.inc.php b/server/lib/classes/aps_installer.inc.php
index 9b601d90b3..2995b01e07 100644
--- a/server/lib/classes/aps_installer.inc.php
+++ b/server/lib/classes/aps_installer.inc.php
@@ -395,7 +395,7 @@ class ApsInstaller extends ApsBase
 						mkdir($this->document_root, 0777, true);
 					}
 				} else {
-					exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
+					$app->system->exec_safe("rm -Rf ?*", $this->local_installpath);
 				}
 			} else {
 				mkdir($this->local_installpath, 0777, true);
@@ -412,7 +412,7 @@ class ApsInstaller extends ApsBase
 					|| ($this->extractZip($this->packages_dir.'/'.$task['path'], 'scripts', $this->local_installpath.'install_scripts/') === false) )
 				{
 					// Clean already extracted data
-					exec("rm -Rf ".escapeshellarg($this->local_installpath).'*');
+					$app->system->exec_safe("rm -Rf ?*", $this->local_installpath);
 					throw new Exception('Unable to extract the package '.$task['path']);
 				}
 
@@ -423,11 +423,11 @@ class ApsInstaller extends ApsBase
 				$owner_res = $app->db->queryOneRecord("SELECT system_user, system_group FROM web_domain WHERE domain = ?", $main_domain['value']);
 				$this->file_owner_user = $owner_res['system_user'];
 				$this->file_owner_group = $owner_res['system_group'];
-				exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
+				$app->system->exec_safe('chown -R ?:? ?', $this->file_owner_user, $this->file_owner_group, $this->local_installpath);
 
 				//* Chown stats directory back
 				if(is_dir($this->local_installpath.'stats')) {
-					exec('chown -R root:root '.escapeshellarg($this->local_installpath.'stats'));
+					$app->system->exec_safe('chown -R root:root ?', $this->local_installpath.'stats');
 				}
 			}
 		}
@@ -554,7 +554,9 @@ class ApsInstaller extends ApsBase
 
 			$shell_retcode = true;
 			$shell_ret = array();
-			exec('php '.escapeshellarg($this->local_installpath.'install_scripts/'.$cfgscript).' install 2>&1', $shell_ret, $shell_retcode);
+			$app->system->exec_safe('php ? install 2>&1', $this->local_installpath.'install_scripts/'.$cfgscript);
+			$shell_ret = $app->system->last_exec_out();
+			$shell_retcode = $app->system->last_exec_retcode();
 			$shell_ret = array_filter($shell_ret);
 			$shell_ret_str = implode("\n", $shell_ret);
 
@@ -566,11 +568,11 @@ class ApsInstaller extends ApsBase
 			else
 			{
 				// The install succeeded, chown newly created files too
-				exec('chown -R '.$this->file_owner_user.':'.$this->file_owner_group.' '.escapeshellarg($this->local_installpath));
+				$app->system->exec_safe('chown -R ?:? ?', $this->file_owner_user, $this->file_owner_group, $this->local_installpath);
 
 				//* Chown stats directory back
 				if(is_dir($this->local_installpath.'stats')) {
-					exec('chown -R root:root '.escapeshellarg($this->local_installpath.'stats'));
+					$app->system->exec_safe('chown -R root:root ?', $this->local_installpath.'stats');
 				}
 
 				$app->dbmaster->query('UPDATE aps_instances SET instance_status = ? WHERE id = ?', INSTANCE_SUCCESS, $task['instance_id']);
@@ -597,8 +599,9 @@ class ApsInstaller extends ApsBase
 	 */
 	private function cleanup($task, $sxe)
 	{
+		global $app;
 		chdir($this->local_installpath);
-		exec("rm -Rf ".escapeshellarg($this->local_installpath).'install_scripts');
+		$app->system->exec_safe("rm -Rf ?", $this->local_installpath.'install_scripts');
 	}
 
 
diff --git a/server/lib/classes/cron.d/100-monitor_email_quota.inc.php b/server/lib/classes/cron.d/100-monitor_email_quota.inc.php
index 75014c347d..8adf7c7253 100644
--- a/server/lib/classes/cron.d/100-monitor_email_quota.inc.php
+++ b/server/lib/classes/cron.d/100-monitor_email_quota.inc.php
@@ -90,7 +90,7 @@ class cronjob_monitor_email_quota extends cronjob {
 				$email_parts = explode('@', $mb['email']);
 				$filename = $mb['maildir'].'/.quotausage';
 				if(!file_exists($filename) && $dovecot) {
-					exec('doveadm quota recalc -u '.$email);
+					$app->system->exec_safe('doveadm quota recalc -u ?', $email);
 				}
 				if(file_exists($filename) && !is_link($filename)) {
 					$quotafile = file($filename);
@@ -99,7 +99,8 @@ class cronjob_monitor_email_quota extends cronjob {
 					$app->log("Mail storage $email: " . $storage_value[1], LOGLEVEL_DEBUG);
 					unset($quotafile);
 				} else {
-					exec('du -s '.escapeshellcmd($mb['maildir']), $out);
+					$app->system->exec_safe('du -s ?', $mb['maildir']);
+					$out = $app->system->last_exec_out();
 					$parts = explode(' ', $out[0]);
 					$data[$email]['used'] = intval($parts[0])*1024;
 					unset($out);
diff --git a/server/lib/classes/cron.d/150-awstats.inc.php b/server/lib/classes/cron.d/150-awstats.inc.php
index 2d281c7d39..0b1cbd5a44 100644
--- a/server/lib/classes/cron.d/150-awstats.inc.php
+++ b/server/lib/classes/cron.d/150-awstats.inc.php
@@ -71,16 +71,16 @@ class cronjob_awstats extends cronjob {
 				$log_folder .= '/' . $subdomain_host;
 				unset($tmp);
 			}
-			$logfile = escapeshellcmd($rec['document_root'].'/' . $log_folder . '/'.$yesterday.'-access.log');
+			$logfile = $rec['document_root'].'/' . $log_folder . '/'.$yesterday.'-access.log';
 			if(!@is_file($logfile)) {
-				$logfile = escapeshellcmd($rec['document_root'].'/' . $log_folder . '/'.$yesterday.'-access.log.gz');
+				$logfile = $rec['document_root'].'/' . $log_folder . '/'.$yesterday.'-access.log.gz';
 				if(!@is_file($logfile)) {
 					continue;
 				}
 			}
 			$web_folder = (($rec['type'] == 'vhostsubdomain' || $rec['type'] == 'vhostalias') ? $rec['web_folder'] : 'web');
-			$domain = escapeshellcmd($rec['domain']);
-			$statsdir = escapeshellcmd($rec['document_root'].'/'.$web_folder.'/stats');
+			$domain = $rec['domain'];
+			$statsdir = $rec['document_root'].'/'.$web_folder.'/stats';
 			$awstats_pl = $web_config['awstats_pl'];
 			$awstats_buildstaticpages_pl = $web_config['awstats_buildstaticpages_pl'];
 
@@ -117,8 +117,8 @@ class cronjob_awstats extends cronjob {
 			}
 
 			if(!@is_dir($statsdir)) mkdir($statsdir);
-			$username = escapeshellcmd($rec['system_user']);
-			$groupname = escapeshellcmd($rec['system_group']);
+			$username = $rec['system_user'];
+			$groupname = $rec['system_group'];
 			chown($statsdir, $username);
 			chgrp($statsdir, $groupname);
 			if(is_link('/var/log/ispconfig/httpd/'.$domain.'/yesterday-access.log')) unlink('/var/log/ispconfig/httpd/'.$domain.'/yesterday-access.log');
@@ -138,7 +138,7 @@ class cronjob_awstats extends cronjob {
 			// awstats_buildstaticpages.pl -update -config=mydomain.com -lang=en -dir=/var/www/domain.com/'.$web_folder.'/stats -awstatsprog=/path/to/awstats.pl
 			// $command = "$awstats_buildstaticpages_pl -update -config='$domain' -lang=".$conf['language']." -dir='$statsdir' -awstatsprog='$awstats_pl'";
 
-			$command = "$awstats_buildstaticpages_pl -month='$awmonth' -year='$awyear' -update -config='$domain' -lang=".$conf['language']." -dir='$statsdir' -awstatsprog='$awstats_pl'";
+			$command = escapeshellcmd($awstats_buildstaticpages_pl) . ' -month=' . escapeshellarg($awmonth) . ' -year=' . escapeshellarg($awyear) . ' -update -config=' . escapeshellarg($domain) . ' -lang=' . escapeshellarg($conf['language']) . ' -dir=' . escapeshellarg($statsdir) . ' -awstatsprog=' . escapeshellarg($awstats_pl);
 
 			if (date("d") == 2) {
 				$awmonth = date("m")-1;
@@ -178,7 +178,7 @@ class cronjob_awstats extends cronjob {
 				chgrp($rec['document_root']."/".$web_folder."/stats/index.php", $rec['system_group']);
 			}
 
-			exec('chown -R '.$username.':'.$groupname.' '.$statsdir);
+			$app->system->exec_safe('chown -R ?:? ?', $username, $groupname, $statsdir);
 		}
 
 
diff --git a/server/lib/classes/cron.d/150-webalizer.inc.php b/server/lib/classes/cron.d/150-webalizer.inc.php
index 0ae05dd682..5d341cefe7 100644
--- a/server/lib/classes/cron.d/150-webalizer.inc.php
+++ b/server/lib/classes/cron.d/150-webalizer.inc.php
@@ -102,11 +102,11 @@ class cronjob_webalizer extends cronjob {
 				}
 			}
 
-			$domain = escapeshellcmd($rec['domain']);
-			$statsdir = escapeshellcmd($rec['document_root'].'/'.(($rec['type'] == 'vhostsubdomain' || $rec['type'] == 'vhostalias') ? $rec['web_folder'] : 'web').'/stats');
+			$domain = $rec['domain'];
+			$statsdir = $rec['document_root'].'/'.(($rec['type'] == 'vhostsubdomain' || $rec['type'] == 'vhostalias') ? $rec['web_folder'] : 'web').'/stats';
 			$webalizer = '/usr/bin/webalizer';
 			$webalizer_conf_main = '/etc/webalizer/webalizer.conf';
-			$webalizer_conf = escapeshellcmd($rec['document_root'].'/log/webalizer.conf');
+			$webalizer_conf = $rec['document_root'].'/log/webalizer.conf';
 
 			if(is_file($statsdir.'/index.php')) unlink($statsdir.'/index.php');
 
@@ -122,13 +122,13 @@ class cronjob_webalizer extends cronjob {
 
 
 			if(!@is_dir($statsdir)) mkdir($statsdir);
-			$username = escapeshellcmd($rec['system_user']);
-			$groupname = escapeshellcmd($rec['system_group']);
+			$username = $rec['system_user'];
+			$groupname = $rec['system_group'];
 			chown($statsdir, $username);
 			chgrp($statsdir, $groupname);
-			exec("$webalizer -c $webalizer_conf -n $domain -s $domain -r $domain -q -T -p -o $statsdir $logfile");
+			$app->system->exec_safe("$webalizer -c ? -n ? -s ? -r ? -q -T -p -o ? ?", $webalizer_conf, $domain, $domain, $domain, $statsdir, $logfile);
 			
-			exec('chown -R '.$username.':'.$groupname.' '.$statsdir);
+			exec('chown -R ?:? ?', $username, $groupname, $statsdir);
 		}
 
 
diff --git a/server/lib/classes/cron.d/200-logfiles.inc.php b/server/lib/classes/cron.d/200-logfiles.inc.php
index 6f38f0b403..d1dbf94291 100644
--- a/server/lib/classes/cron.d/200-logfiles.inc.php
+++ b/server/lib/classes/cron.d/200-logfiles.inc.php
@@ -54,7 +54,7 @@ class cronjob_logfiles extends cronjob {
 		$server_config = $app->getconf->get_server_config($conf['server_id'], 'server');
 		
 		if($server_config['log_retention'] > 0) {
-			$max_syslog = $server_config['log_retention'];
+			$max_syslog = $app->functions->intval($server_config['log_retention']);
 		} else {
 			$max_syslog = 10;
 		}
@@ -113,18 +113,18 @@ class cronjob_logfiles extends cronjob {
 			}
 
 			$yesterday2 = date('Ymd', time() - 86400*2);
-			$logfile = escapeshellcmd($rec['document_root'].'/' . $log_folder . '/'.$yesterday2.'-access.log');
+			$logfile = $rec['document_root'].'/' . $log_folder . '/'.$yesterday2.'-access.log';
 
 			//* Compress logfile
 			if(@is_file($logfile)) {
 				// Compress yesterdays logfile
-				exec("gzip -c $logfile > $logfile.gz");
+				$app->system->exec_safe("gzip -c ? > ?", $logfile, $logfile . '.gz');
 				unlink($logfile);
 			}
 			
 			$cron_logfiles = array('cron.log', 'cron_error.log', 'cron_wget.log');
 			foreach($cron_logfiles as $cron_logfile) {
-				$cron_logfile = escapeshellcmd($rec['document_root'].'/' . $log_folder . '/' . $cron_logfile);
+				$cron_logfile = $rec['document_root'].'/' . $log_folder . '/' . $cron_logfile;
 				
 				// rename older files (move up by one)
 				$num = $log_retention;
@@ -135,8 +135,8 @@ class cronjob_logfiles extends cronjob {
 				
 				// compress current logfile
 				if(is_file($cron_logfile)) {
-					exec("gzip -c $cron_logfile > $cron_logfile.1.gz");
-					exec("cat /dev/null > $cron_logfile");
+					$app->system->exec_safe("gzip -c ? > ?", $cron_logfile, $cron_logfile . '.1.gz');
+					$app->system->exec_safe("cat /dev/null > ?", $cron_logfile);
 				}
 				// remove older logs
 				$num = $log_retention;
@@ -156,8 +156,8 @@ class cronjob_logfiles extends cronjob {
 			}
 			// compress current logfile
 			if(is_file($error_logfile)) {
-				exec("gzip -c $error_logfile > $error_logfile.1.gz");
-				exec("cat /dev/null > $error_logfile");
+				$app->system->exec_safe("gzip -c ? > ?", $error_logfile, $error_logfile . '.1.gz');
+				$app->system->exec_safe("cat /dev/null > ?", $error_logfile);
 			}
 
 			// delete logfiles after x days (default 10)
@@ -175,7 +175,7 @@ class cronjob_logfiles extends cronjob {
 		//* Delete old logfiles in /var/log/ispconfig/httpd/ that were created by vlogger for the hostname of the server
 		exec('hostname -f', $tmp_hostname);
 		if($tmp_hostname[0] != '' && is_dir('/var/log/ispconfig/httpd/'.$tmp_hostname[0])) {
-			exec('cd /var/log/ispconfig/httpd/'.$tmp_hostname[0]."; find . -mtime +$max_syslog -name '*.log' | xargs rm > /dev/null 2> /dev/null");
+			$app->system->exec_safe("cd ?; find . -mtime +$max_syslog -name '*.log' | xargs rm > /dev/null 2> /dev/null", '/var/log/ispconfig/httpd/'.$tmp_hostname[0]);
 		}
 		unset($tmp_hostname);
 
@@ -195,8 +195,8 @@ class cronjob_logfiles extends cronjob {
 			}
 			// compress current logfile
 			if(is_file($ispconfig_logfile)) {
-				exec("gzip -c $ispconfig_logfile > $ispconfig_logfile.1.gz");
-				exec("cat /dev/null > $ispconfig_logfile");
+				$app->system->exec_safe("gzip -c ? > ?", $ispconfig_logfile, $ispconfig_logfile . '.1.gz');
+				$app->system->exec_safe("cat /dev/null > ?", $ispconfig_logfile);
 			}
 			// remove older logs
 			$num = $max_syslog;
@@ -215,9 +215,9 @@ class cronjob_logfiles extends cronjob {
 		$app->uses('system');
 		if(is_array($records)) {
 			foreach($records as $rec){
-				$tmp_path = realpath(escapeshellcmd($rec['document_root'].'/tmp'));
+				$tmp_path = realpath($rec['document_root'].'/tmp');
 				if($tmp_path != '' && strlen($tmp_path) > 10 && is_dir($tmp_path) && $app->system->is_user($rec['system_user'])){
-					exec('cd '.$tmp_path."; find . -mtime +1 -name 'sess_*' | grep -v -w .no_delete | xargs rm > /dev/null 2> /dev/null");
+					exec("cd ?; find . -mtime +1 -name 'sess_*' | grep -v -w .no_delete | xargs rm > /dev/null 2> /dev/null", $tmp_path);
 				}
 			}
 		}
diff --git a/server/lib/classes/cron.d/500-backup.inc.php b/server/lib/classes/cron.d/500-backup.inc.php
index 77b355fe6e..579e0174ba 100644
--- a/server/lib/classes/cron.d/500-backup.inc.php
+++ b/server/lib/classes/cron.d/500-backup.inc.php
@@ -69,9 +69,9 @@ class cronjob_backup extends cronjob {
 			}
 
 			if(!is_dir($backup_dir)) {
-				mkdir(escapeshellcmd($backup_dir), $backup_dir_permissions, true);
+				mkdir($backup_dir, $backup_dir_permissions, true);
 			} else {
-				chmod(escapeshellcmd($backup_dir), $backup_dir_permissions);
+				chmod($backup_dir, $backup_dir_permissions);
 			}
             $run_backups = true;
             //* mount backup directory, if necessary
@@ -127,16 +127,20 @@ class cronjob_backup extends cronjob {
 							if($backup_mode == 'userzip') {
 								//* Create a .zip backup as web user and include also files owned by apache / nginx user
 								$web_backup_file = 'web'.$web_id.'_'.date('Y-m-d_H-i').'.zip';
-								exec('cd '.escapeshellarg($web_path).' && sudo -u '.escapeshellarg($web_user).' find . -group '.escapeshellarg($web_group).' -print 2> /dev/null | zip -b '.escapeshellarg($backup_tmp).' --exclude=./backup\*'.$backup_excludes.' --symlinks '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' -@', $tmp_output, $retval);
-								if($retval == 0 || $retval == 12) exec('cd '.escapeshellarg($web_path).' && sudo -u '.escapeshellarg($web_user).' find . -user '.escapeshellarg($http_server_user).' -print 2> /dev/null | zip -b '.escapeshellarg($backup_tmp).' --exclude=./backup\*'.$backup_excludes.' --update --symlinks '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' -@', $tmp_output, $retval);
+								$app->system->exec_safe('cd ? && sudo -u ? find . -group ? -print 2> /dev/null | zip -b ? --exclude=./backup\*'.$backup_excludes.' --symlinks ? -@', $web_path, $web_user, $web_group, $backup_tmp, $web_backup_dir.'/'.$web_backup_file);
+								$retval = $app->system->last_exec_retcode();
+								if($retval == 0 || $retval == 12) $app->system->exec_safe('cd ? && sudo -u ? find . -user ? -print 2> /dev/null | zip -b ? --exclude=./backup\*'.$backup_excludes.' --update --symlinks ? -@', $web_path, $web_user, $http_server_user, $backup_tmp, $web_backup_dir.'/'.$web_backup_file);
+								$retval = $app->system->last_exec_retcode();
 							} else {
 								//* Create a tar.gz backup as root user
 								$web_backup_file = 'web'.$web_id.'_'.date('Y-m-d_H-i').'.tar.gz';
 								if ($use_pigz) {
-									exec('tar pcf - --directory '.escapeshellarg($web_path).' . --exclude=./backup\*'.$backup_excludes.' | pigz > '.escapeshellarg($web_backup_dir.'/'.$web_backup_file), $tmp_output, $retval);
+									$app->system->exec_safe('tar pcf - --directory ? . --exclude=./backup\*'.$backup_excludes.' | pigz > ?', $web_path, $web_backup_dir.'/'.$web_backup_file);
+									$retval = $app->system->last_exec_retcode();
 								} else {
-									exec('tar pczf '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' --exclude=./backup\*'.$backup_excludes.' --directory '.escapeshellarg($web_path).' .', $tmp_output, $retval);
-}
+									$app->system->exec_safe('tar pczf ? --exclude=./backup\*'.$backup_excludes.' --directory ? .', $web_backup_dir.'/'.$web_backup_file, $web_path);
+									$retval = $app->system->last_exec_retcode();
+								}
 							}
 							if($retval == 0 || ($backup_mode != 'userzip' && $retval == 1) || ($backup_mode == 'userzip' && $retval == 12)) { // tar can return 1, zip can return 12(due to harmless warings) and still create valid backups  
 								if(is_file($web_backup_dir.'/'.$web_backup_file)){
@@ -256,13 +260,16 @@ class cronjob_backup extends cronjob {
 							$db_id = $rec['database_id'];
 							$db_name = $rec['database_name'];
 							$db_backup_file = 'db_'.$db_name.'_'.date('Y-m-d_H-i').'.sql';
-							//$command = "mysqldump -h '".escapeshellcmd($clientdb_host)."' -u '".escapeshellcmd($clientdb_user)."' -p'".escapeshellcmd($clientdb_password)."' -c --add-drop-table --create-options --quick --result-file='".$db_backup_dir.'/'.$db_backup_file."' '".$db_name."'";
-							$command = "mysqldump -h ".escapeshellarg($clientdb_host)." -u ".escapeshellarg($clientdb_user)." -p".escapeshellarg($clientdb_password)." -c --add-drop-table --create-options --quick --max_allowed_packet=512M ".$mysqldump_routines." --result-file='".$db_backup_dir.'/'.$db_backup_file."' '".$db_name."'";
-							exec($command, $tmp_output, $retval);
-
+							$command = "mysqldump -h ? -u ? -p? -c --add-drop-table --create-options --quick --max_allowed_packet=512M ".$mysqldump_routines." --result-file=? ?";
+							$app->system->exec_safe($command, $clientdb_host, $clientdb_user, $clientdb_password, $db_backup_dir.'/'.$db_backup_file, $db_name);
+							$retval = $app->system->last_exec_retcode();
+							
 							//* Compress the backup with gzip / pigz
-							if($retval == 0) exec("$zip_cmd -c '".escapeshellcmd($db_backup_dir.'/'.$db_backup_file)."' > '".escapeshellcmd($db_backup_dir.'/'.$db_backup_file).".gz'", $tmp_output, $retval);
-
+							if($retval == 0) {
+								$app->system->exec_safe("$zip_cmd -c ? > ?", $db_backup_dir.'/'.$db_backup_file, $db_backup_dir.'/'.$db_backup_file . '.gz');
+								$retval = $app->system->last_exec_retcode();
+							}
+							
 							if($retval == 0){
 								if(is_file($db_backup_dir.'/'.$db_backup_file.'.gz')){
 									chmod($db_backup_dir.'/'.$db_backup_file.'.gz', 0750);
diff --git a/server/lib/classes/cron.d/500-backup_mail.inc.php b/server/lib/classes/cron.d/500-backup_mail.inc.php
index b05caf70d7..6cd689edca 100644
--- a/server/lib/classes/cron.d/500-backup_mail.inc.php
+++ b/server/lib/classes/cron.d/500-backup_mail.inc.php
@@ -122,24 +122,28 @@ class cronjob_backup_mail extends cronjob {
 						if ($rec['maildir_format'] == 'mdbox') {
 							if (empty($this->tmp_backup_dir)) $this->tmp_backup_dir = $rec['maildir'];
 							// Create temporary backup-mailbox
-							exec("su -c 'dsync backup -u \"".$rec["email"]."\" mdbox:".$this->tmp_backup_dir."/backup'", $tmp_output, $retval);
+							exec("su -c ?", 'dsync backup -u "'.$rec["email"].'" mdbox:' . $this->tmp_backup_dir . '/backup');
 		
 							if($backup_mode == 'userzip') {
 								$mail_backup_file.='.zip';
-								exec('cd '.$this->tmp_backup_dir.' && zip '.$mail_backup_dir.'/'.$mail_backup_file.' -b '.escapeshellarg($backup_tmp).' -r backup > /dev/null && rm -rf backup', $tmp_output, $retval);
-							}
-							else {
+								$app->system->exec_safe('cd ? && zip ? -b ? -r backup > /dev/null && rm -rf backup', $this->tmp_backup_dir, $mail_backup_dir.'/'.$mail_backup_file, $backup_tmp);
+								$retval = $app->system->last_exec_retcode();
+							} else {
 								$mail_backup_file.='.tar.gz';
 								if ($use_pigz) {
-									exec('tar pcf - --directory '.escapeshellarg($this->tmp_backup_dir).' backup | pigz > '.$mail_backup_dir.'/'.$mail_backup_file.' && rm -rf '.$this->tmp_backup_dir.'/backup', $tmp_output, $retval);
+									$app->system->exec_safe('tar pcf - --directory ? backup | pigz > ? && rm -rf ?', $this->tmp_backup_dir, $mail_backup_dir.'/'.$mail_backup_file, $this->tmp_backup_dir.'/backup');
+									$retval = $app->system->last_exec_retcode();
 								} else {
-									exec(escapeshellcmd('tar pczf '.$mail_backup_dir.'/'.$mail_backup_file.' --directory '.$this->tmp_backup_dir.' backup && rm -rf '.$this->tmp_backup_dir.'/backup'), $tmp_output, $retval);
+									$app->system->exec_safe('tar pczf ? --directory ? backup && rm -rf ?', $mail_backup_dir.'/'.$mail_backup_file, $this->tmp_backup_dir, $this->tmp_backup_dir.'/backup');
+									$retval = $app->system->last_exec_retcode();
 								}
 							}
 							
 							if ($retval != 0) {
 								// Cleanup
-								if (file_exists($this->tmp_backup_dir.'/backup')) exec('rm -rf '.$this->tmp_backup_dir.'/backup');
+								if(file_exists($this->tmp_backup_dir . '/backup')) {
+									$app->system->exec_safe('rm -rf ?', $this->tmp_backup_dir . '/backup');
+								}
 							}
 						}
 						else {
@@ -154,15 +158,17 @@ class cronjob_backup_mail extends cronjob {
 							//* create archives
 							if($backup_mode == 'userzip') {
 								$mail_backup_file.='.zip';
-								exec('cd '.$domain_dir.' && zip '.$mail_backup_dir.'/'.$mail_backup_file.' -b '.escapeshellarg($backup_tmp).' -r '.$source_dir.' > /dev/null', $tmp_output, $retval);
+								$app->system->exec_safe('cd ? && zip ? -b ? -r ? > /dev/null', $domain_dir, $mail_backup_dir.'/'.$mail_backup_file, $backup_tmp, $source_dir);
+								$retval = $app->system->last_exec_retcode();
 							} else {
 								/* Create a tar.gz backup */
 								$mail_backup_file.='.tar.gz';
 								if ($use_pigz) {
-									exec('tar pcf - --directory '.escapeshellarg($domain_dir).' '.escapeshellarg($source_dir).' | pigz > '.$mail_backup_dir.'/'.$mail_backup_file, $tmp_output, $retval);
+									$app->system->exec_safe('tar pcf - --directory ? ? | pigz > ?', $domain_dir, $source_dir, $mail_backup_dir.'/'.$mail_backup_file);
 								} else {
-									exec(escapeshellcmd('tar pczf '.$mail_backup_dir.'/'.$mail_backup_file.' --directory '.$domain_dir.' '.$source_dir), $tmp_output, $retval);
+									$app->system->exec_safe('tar pczf ? --directory ? ?', $mail_backup_dir.'/'.$mail_backup_file, $domain_dir, $source_dir);
 								}
+								$retval = $app->system->last_exec_retcode();
 							}
 						}
 						
@@ -181,7 +187,9 @@ class cronjob_backup_mail extends cronjob {
 							if(is_file($mail_backup_dir.'/'.$mail_backup_file)) unlink($mail_backup_dir.'/'.$mail_backup_file);
 							// And remove backup-mdbox
 							if ($rec['maildir_format'] == 'mdbox') {
-								if(file_exists($rec['maildir'].'/backup'))  exec("su -c 'rm -rf ".$rec['maildir']."/backup'");
+								if(file_exists($rec['maildir'] . '/backup')) {
+									$app->system->exec_safe('rm -rf ?', $rec['maildir'] . '/backup');
+								}
 							}
 							$app->log($mail_backup_file.' NOK:'.implode('',$tmp_output), LOGLEVEL_WARN);
 						}
diff --git a/server/lib/classes/cron.d/600-purge_mailboxes.inc.php b/server/lib/classes/cron.d/600-purge_mailboxes.inc.php
index 59775fb7be..451eb56642 100644
--- a/server/lib/classes/cron.d/600-purge_mailboxes.inc.php
+++ b/server/lib/classes/cron.d/600-purge_mailboxes.inc.php
@@ -58,7 +58,7 @@ class cronjob_purge_mailboxes extends cronjob {
 		
 		if(is_array($records)) {
 			foreach($records as $rec){
-				exec("su -c 'doveadm purge -u \"".$rec["email"]."\"'");
+				$app->system->exec_safe("su -c ?", 'doveadm purge -u "' . $rec["email"] . '"');
 			}
 		}
 
diff --git a/server/lib/classes/cron.d/900-letsencrypt.inc.php b/server/lib/classes/cron.d/900-letsencrypt.inc.php
index 30a23fe973..3e2c9190c6 100644
--- a/server/lib/classes/cron.d/900-letsencrypt.inc.php
+++ b/server/lib/classes/cron.d/900-letsencrypt.inc.php
@@ -66,7 +66,7 @@ class cronjob_letsencrypt extends cronjob {
 					} else {
 						$marker_file = '/usr/local/ispconfig/server/le.restart';
 						$cmd = "echo '1' > " . $marker_file;
-						exec($letsencrypt . ' -n renew --post-hook ' . escapeshellarg($cmd));
+						$app->system->exec_safe($letsencrypt . ' -n renew --post-hook ?', $cmd);
 						if(file_exists($marker_file) && trim(file_get_contents($marker_file)) == '1') {
 							unlink($marker_file);
 							$app->services->restartServiceDelayed('httpd', 'force-reload');
diff --git a/server/lib/classes/functions.inc.php b/server/lib/classes/functions.inc.php
index e36ed5b04f..1d9dd67569 100644
--- a/server/lib/classes/functions.inc.php
+++ b/server/lib/classes/functions.inc.php
@@ -425,9 +425,9 @@ class functions {
 		if(file_exists($id_rsa_file)) unset($id_rsa_file);
 		if(file_exists($id_rsa_pub_file)) unset($id_rsa_pub_file);
 		if(!file_exists($id_rsa_file) && !file_exists($id_rsa_pub_file)) {
-			exec('ssh-keygen -t rsa -C '.$username.'-rsa-key-'.time().' -f '.$id_rsa_file.' -N ""');
+			$app->system->exec_safe('ssh-keygen -t rsa -C ? -f ? -N ""', $username.'-rsa-key-'.time(), $id_rsa_file);
 			$app->db->query("UPDATE client SET created_at = UNIX_TIMESTAMP(), id_rsa = ?, ssh_rsa = ? WHERE client_id = ?", $app->system->file_get_contents($id_rsa_file), $app->system->file_get_contents($id_rsa_pub_file), $client_id);
-			exec('rm -f '.$id_rsa_file.' '.$id_rsa_pub_file);
+			$app->system->exec_safe('rm -f ? ?', $id_rsa_file, $id_rsa_pub_file);
 		} else {
 			$app->log("Failed to create SSH keypair for ".$username, LOGLEVEL_WARN);
 		}
diff --git a/server/lib/classes/letsencrypt.inc.php b/server/lib/classes/letsencrypt.inc.php
index 583e1c25bb..62080e29b4 100644
--- a/server/lib/classes/letsencrypt.inc.php
+++ b/server/lib/classes/letsencrypt.inc.php
@@ -389,7 +389,7 @@ class letsencrypt {
 			}
 
 			if(@is_link($key_file)) $app->system->unlink($key_file);
-			if(@file_exists($key_tmp_file)) exec("ln -s ".escapeshellcmd($key_tmp_file)." ".escapeshellcmd($key_file));
+			if(@file_exists($key_tmp_file)) $app->system->exec_safe("ln -s ? ?", $key_tmp_file, $key_file);
 
 			if(is_file($crt_file)) {
 				$app->system->copy($crt_file, $crt_file.'.old.'.$date);
@@ -398,7 +398,7 @@ class letsencrypt {
 			}
 
 			if(@is_link($crt_file)) $app->system->unlink($crt_file);
-			if(@file_exists($crt_tmp_file))exec("ln -s ".escapeshellcmd($crt_tmp_file)." ".escapeshellcmd($crt_file));
+			if(@file_exists($crt_tmp_file))$app->system->exec_safe("ln -s ? ?", $crt_tmp_file, $crt_file);
 
 			if(is_file($bundle_file)) {
 				$app->system->copy($bundle_file, $bundle_file.'.old.'.$date);
@@ -407,7 +407,7 @@ class letsencrypt {
 			}
 
 			if(@is_link($bundle_file)) $app->system->unlink($bundle_file);
-			if(@file_exists($bundle_tmp_file)) exec("ln -s ".escapeshellcmd($bundle_tmp_file)." ".escapeshellcmd($bundle_file));
+			if(@file_exists($bundle_tmp_file)) $app->system->exec_safe("ln -s ? ?", $bundle_tmp_file, $bundle_file);
 			
 			return true;
 		} else {
diff --git a/server/lib/classes/monitor_tools.inc.php b/server/lib/classes/monitor_tools.inc.php
index 1d3dab290b..fefdbcd681 100644
--- a/server/lib/classes/monitor_tools.inc.php
+++ b/server/lib/classes/monitor_tools.inc.php
@@ -593,13 +593,12 @@ class monitor_tools {
 
 		// Getting the logfile content
 		if ($logfile != '') {
-			$logfile = escapeshellcmd($logfile);
 			if (stristr($logfile, ';') or substr($logfile, 0, 9) != '/var/log/' or stristr($logfile, '..')) {
 				$log = 'Logfile path error.';
 			} else {
 				$log = '';
 				if (is_readable($logfile)) {
-					$fd = popen('tail -n 100 ' . $logfile, 'r');
+					$fd = popen('tail -n 100 ' . escapeshellarg($logfile), 'r');
 					if ($fd) {
 						while (!feof($fd)) {
 							$log .= fgets($fd, 4096);
diff --git a/server/lib/classes/system.inc.php b/server/lib/classes/system.inc.php
index 5c277ada1a..9f3e963eea 100644
--- a/server/lib/classes/system.inc.php
+++ b/server/lib/classes/system.inc.php
@@ -37,6 +37,9 @@ class system{
 	var $min_uid = 500;
 	var $min_gid = 500;
 	
+	private $_last_exec_out = null;
+	private $_last_exec_retcode = null;
+	
 	/**
 	 * Construct for this class
 	 *
@@ -716,8 +719,10 @@ class system{
 	function posix_getgrnam($group) {
 		if(!function_exists('posix_getgrnam')){
 			$group_datei = $this->server_conf['group_datei'];
-			$cmd = 'grep -m 1 "^'.$group.':" '.$group_datei;
-			exec($cmd, $output, $return_var);
+			$cmd = 'grep -m 1 ? ?';
+			$this->exec_safe($cmd, '^'.$group.':', $group_datei);
+			$output = $this->last_exec_out();
+			$return_var = $this->last_exec_retcode();
 			if($return_var != 0 || !$output[0]) return false;
 			list($f1, $f2, $f3, $f4) = explode(':', $output[0]);
 			$f2 = trim($f2);
@@ -1073,10 +1078,10 @@ class system{
 		} else { // Linux
 			if(substr($dist, 0, 4) == 'suse'){
 				if($action == 'on'){
-					exec("chkconfig --add $service &> /dev/null");
+					$this->exec_safe("chkconfig --add ? &> /dev/null", $service);
 				}
 				if($action == 'off'){
-					exec("chkconfig --del $service &> /dev/null");
+					$this->exec_safe("chkconfig --del ? &> /dev/null", $service);
 				}
 			} else {
 				$runlevels = explode(',', $rl);
@@ -1375,7 +1380,7 @@ class system{
 					if(!empty($ifconfig['IP'])){
 						foreach($ifconfig['IP'] as $key => $val){
 							if(!strstr($val, 'lo') && !strstr($val, 'lp') && strstr($val, $main_interface)){
-								exec('ifconfig '.$val.' down &> /dev/null');
+								$this->exec_safe('ifconfig ? down &> /dev/null', $val);
 								unset($ifconfig['INTERFACE'][$val]);
 							}
 						}
@@ -1391,7 +1396,7 @@ class system{
 									$i = -1;
 								}
 							}
-							exec('ifconfig '.$new_interface.' '.$to.' netmask '.$this->server_conf['server_netzmaske'].' up &> /dev/null');
+							$this->exec_safe('ifconfig ? ? netmask ? up &> /dev/null', $new_interface, $to, $this->server_conf['server_netzmaske']);
 							$ifconfig['INTERFACE'][$new_interface] = $to;
 						}
 					}
@@ -1627,15 +1632,6 @@ class system{
 
 		chmod($dir, 0700);
 
-		/*
-		if($user != '' && $this->is_user($user) && $user != 'root') {
-			$user = escapeshellcmd($user);
-			// I assume that the name of the (vmail group) is the same as the name of the mail user in ISPConfig 3
-			$group = $user;
-			exec("chown $user:$group $dir $dir_cur $dir_new $dir_tmp");
-		}
-		*/
-
 		//* Add the subfolder to the subscriptions and courierimapsubscribed files
 		if($subfolder != '') {
 			
@@ -1698,7 +1694,9 @@ class system{
 
 	//* Check if a application is installed
 	function is_installed($appname) {
-		exec('which '.escapeshellcmd($appname).' 2> /dev/null', $out, $returncode);
+		$this->exec_safe('which ? 2> /dev/null', $appname);
+		$out = $this->last_exec_out();
+		$returncode = $this->last_exec_retcode();
 		if(isset($out[0]) && stristr($out[0], $appname) && $returncode == 0) {
 			return true;
 		} else {
@@ -1720,10 +1718,10 @@ class system{
 
 		if($protect == true && $web_config['web_folder_protection'] == 'y') {
 			//* Add protection
-			if($document_root != '' && $document_root != '/' && strlen($document_root) > 6 && !stristr($document_root, '..')) exec('chattr +i '.escapeshellcmd($document_root));
+			if($document_root != '' && $document_root != '/' && strlen($document_root) > 6 && !stristr($document_root, '..')) $this->exec_safe('chattr +i ?', $document_root);
 		} else {
 			//* Remove protection
-			if($document_root != '' && $document_root != '/' && strlen($document_root) > 6 && !stristr($document_root, '..')) exec('chattr -i '.escapeshellcmd($document_root));
+			if($document_root != '' && $document_root != '/' && strlen($document_root) > 6 && !stristr($document_root, '..')) $this->exec_safe('chattr -i ?', $document_root);
 		}
 	}
 
@@ -1835,8 +1833,9 @@ class system{
 
 	function is_mounted($mountpoint){
 		//$cmd = 'df 2>/dev/null | grep " '.$mountpoint.'$"';
-		$cmd = 'mount 2>/dev/null | grep " on '.$mountpoint.' type "';
-		exec($cmd, $output, $return_var);
+		$cmd = 'mount 2>/dev/null | grep ?';
+		exec($cmd, ' on '. $mountpoint . ' type ');
+		$return_var = $this->last_exec_retcode();
 		return $return_var == 0 ? true : false;
 	}
 
@@ -1908,7 +1907,8 @@ class system{
 		// systemd
 		if(is_executable('/bin/systemd') || is_executable('/usr/bin/systemctl')){
 			if ($check_service) {
-				exec("systemctl is-enabled ".$servicename." 2>&1", $out, $ret_val);
+				$this->exec_safe("systemctl is-enabled ? 2>&1", $servicename);
+				$ret_val = $this->last_exec_retcode();
 			}
 			if ($ret_val == 0 || !$check_service) {
 				return 'systemctl '.$action.' '.$servicename.'.service';
@@ -2049,6 +2049,42 @@ class system{
 		return true;
 	}
 	
+	public function last_exec_out() {
+		return $this->_last_exec_out;
+	}
+	
+	public function last_exec_retcode() {
+		return $this->_last_exec_retcode;
+	}
+	
+	public function exec_safe($cmd) {
+		$arg_count = func_num_args();
+		if($arg_count > 1) {
+			$args = func_get_args();
+
+			$pos = 0;
+			$a = 0;
+			foreach($args as $value) {
+				$a++;
+				
+				$pos = strpos($cmd, '?', $pos);
+				if($pos === false) {
+					break;
+				}
+				$value = escapeshellarg($value);
+				$cmd = substr_replace($cmd, $value, $pos, 1);
+				$pos += strlen($value);
+			}
+		}
+		
+		$this->_last_exec_out = null;
+		$this->_last_exec_retcode = null;
+		return exec($cmd, $this->_last_exec_out, $this->_last_exec_retcode);
+	}
+	
+	public function system_safe($cmd) {
+		call_user_func_array(array($this, 'exec_safe'), func_get_args());
+		return implode("\n", $this->_last_exec_out);
+	}
+	
 }
-
-?>
diff --git a/server/mods-available/remoteaction_core_module.inc.php b/server/mods-available/remoteaction_core_module.inc.php
index 807de5060a..e0ce33a5e4 100644
--- a/server/mods-available/remoteaction_core_module.inc.php
+++ b/server/mods-available/remoteaction_core_module.inc.php
@@ -152,10 +152,10 @@ class remoteaction_core_module {
 					$template_cache_dir = '/vz/template/cache/';
 					$template_name = escapeshellcmd($parts[1]);
 					if($veid > 0 && $template_name != '' && is_dir($template_cache_dir)) {
-						$command = "vzdump --suspend --compress --stdexcludes --dumpdir $template_cache_dir $veid";
-						exec($command);
-						exec("mv ".$template_cache_dir."vzdump-openvz-".$veid."*.tgz ".$template_cache_dir.$template_name.".tar.gz");
-						exec("rm -f ".$template_cache_dir."vzdump-openvz-".$veid."*.log");
+						$command = "vzdump --suspend --compress --stdexcludes --dumpdir ? ?";
+						$app->system->exec_safe($command, $template_cache_dir, $veid);
+						$app->system->exec_safe("mv ?*.tgz ?", $template_cache_dir."vzdump-openvz-".$veid, $template_cache_dir.$template_name.".tar.gz");
+						$app->system->exec_safe("rm -f ?*.log", $template_cache_dir."vzdump-openvz-".$veid);
 					}
 					$this->_actionDone($action['action_id'], 'ok');
 					/* this action takes so much time,
@@ -191,7 +191,8 @@ class remoteaction_core_module {
 	}
 
 	private function _doIspCUpdate($action) {
-
+		global $app;
+		
 		// Ensure that this code is not executed twice as this would cause a loop in case of a failure
 		$this->_actionDone($action['action_id'], 'ok');
 
@@ -210,14 +211,14 @@ class remoteaction_core_module {
 		chdir("/tmp");
 
 		/* delete the old files (if there are any...) */
-		exec("rm /tmp/ISPConfig-" . $new_version . ".tar.gz");
+		$app->system->exec_safe("rm ?", "/tmp/ISPConfig-" . $new_version . ".tar.gz");
 		exec("rm /tmp/ispconfig3_install -R");
 
 		/* get the newest version */
-		exec("wget http://www.ispconfig.org/downloads/ISPConfig-" . $new_version . ".tar.gz");
+		$app->system->exec_safe("wget ?", "http://www.ispconfig.org/downloads/ISPConfig-" . $new_version . ".tar.gz");
 
 		/* extract the files */
-		exec("tar xvfz ISPConfig-" . $new_version . ".tar.gz");
+		$app->system->exec_safe("tar xvfz ?", "ISPConfig-" . $new_version . ".tar.gz");
 
 		/*
 		 * Initialize the automated update
@@ -229,7 +230,7 @@ class remoteaction_core_module {
 		/*
 		 * do some clean-up
 		 */
-		exec("rm /tmp/ISPConfig-" . $new_version . ".tar.gz");
+		$app->system->exec_safe("rm ?", "/tmp/ISPConfig-" . $new_version . ".tar.gz");
 
 		/*
 		 * go back to the "old path"
diff --git a/server/plugins-available/apache2_plugin.inc.php b/server/plugins-available/apache2_plugin.inc.php
index 7ecbb76109..28a3c4d5d6 100644
--- a/server/plugins-available/apache2_plugin.inc.php
+++ b/server/plugins-available/apache2_plugin.inc.php
@@ -332,36 +332,38 @@ class apache2_plugin {
 			$ssl_cnf_file = $ssl_dir.'/openssl.conf';
 			$app->system->file_put_contents($ssl_cnf_file, $ssl_cnf);
 
-			$rand_file = escapeshellcmd($rand_file);
-			$key_file2 = escapeshellcmd($key_file2);
+			$rand_file = $rand_file;
+			$key_file2 = $key_file2;
 			$openssl_cmd_key_file2 = $key_file2;
 			if(substr($domain, 0, 2) == '*.' && strpos($key_file2, '/ssl/\*.') !== false) $key_file2 = str_replace('/ssl/\*.', '/ssl/*.', $key_file2); // wildcard certificate
-			$key_file = escapeshellcmd($key_file);
+			$key_file = $key_file;
 			$openssl_cmd_key_file = $key_file;
 			if(substr($domain, 0, 2) == '*.' && strpos($key_file, '/ssl/\*.') !== false) $key_file = str_replace('/ssl/\*.', '/ssl/*.', $key_file); // wildcard certificate
 			$ssl_days = 3650;
-			$csr_file = escapeshellcmd($csr_file);
+			$csr_file = $csr_file;
 			$openssl_cmd_csr_file = $csr_file;
 			if(substr($domain, 0, 2) == '*.' && strpos($csr_file, '/ssl/\*.') !== false) $csr_file = str_replace('/ssl/\*.', '/ssl/*.', $csr_file); // wildcard certificate
-			$config_file = escapeshellcmd($ssl_cnf_file);
-			$crt_file = escapeshellcmd($crt_file);
+			$config_file = $ssl_cnf_file;
+			$crt_file = $crt_file;
 			$openssl_cmd_crt_file = $crt_file;
 			if(substr($domain, 0, 2) == '*.' && strpos($crt_file, '/ssl/\*.') !== false) $crt_file = str_replace('/ssl/\*.', '/ssl/*.', $crt_file); // wildcard certificate
 
 			if(is_file($ssl_cnf_file) && !is_link($ssl_cnf_file)) {
 
-				exec("openssl genrsa -des3 -rand $rand_file -passout pass:$ssl_password -out $openssl_cmd_key_file2 2048");
-				exec("openssl req -new -sha256 -passin pass:$ssl_password -passout pass:$ssl_password -key $openssl_cmd_key_file2 -out $openssl_cmd_csr_file -days $ssl_days -config $config_file");
-				exec("openssl rsa -passin pass:$ssl_password -in $openssl_cmd_key_file2 -out $openssl_cmd_key_file");
+				$app->system->exec_safe("openssl genrsa -des3 -rand ? -passout pass:? -out ? 2048", $rand_file, $ssl_password, $openssl_cmd_key_file2);
+				$app->system->exec_safe("openssl req -new -sha256 -passin pass:? -passout pass:? -key ? -out ? -days ? -config ?", $ssl_password, $ssl_password, $openssl_cmd_key_file2, $openssl_cmd_csr_file, $ssl_days, $config_file);
+				$app->system->exec_safe("openssl rsa -passin pass:? -in ? -out ?", $ssl_password, $openssl_cmd_key_file2, $openssl_cmd_key_file);
 
 				if(file_exists($web_config['CA_path'].'/openssl.cnf'))
 				{
-					exec("openssl ca -batch -out $openssl_cmd_crt_file -config ".$web_config['CA_path']."/openssl.cnf -passin pass:".$web_config['CA_pass']." -in $openssl_cmd_csr_file");
+					$app->system->exec_safe("openssl ca -batch -out ? -config ? -passin pass:? -in ?", $openssl_cmd_crt_file, $web_config['CA_path']."/openssl.cnf", $web_config['CA_pass'], $openssl_cmd_csr_file);
 					$app->log("Creating CA-signed SSL Cert for: $domain", LOGLEVEL_DEBUG);
-					if (filesize($crt_file)==0 || !file_exists($crt_file)) $app->log("CA-Certificate signing failed.  openssl ca -out $openssl_cmd_crt_file -config ".$web_config['CA_path']."/openssl.cnf -passin pass:".$web_config['CA_pass']." -in $openssl_cmd_csr_file", LOGLEVEL_ERROR);
+					if(filesize($crt_file) == 0 || !file_exists($crt_file)) {
+						$app->log("CA-Certificate signing failed.  openssl ca -out $openssl_cmd_crt_file -config " . $web_config['CA_path'] . "/openssl.cnf -passin pass:" . $web_config['CA_pass'] . " -in $openssl_cmd_csr_file", LOGLEVEL_ERROR);
+					}
 				};
 				if (@filesize($crt_file)==0 || !file_exists($crt_file)){
-					exec("openssl req -x509 -passin pass:$ssl_password -passout pass:$ssl_password -key $openssl_cmd_key_file2 -in $openssl_cmd_csr_file -out $openssl_cmd_crt_file -days $ssl_days -config $config_file ");
+					$app->system->exec_safe("openssl req -x509 -passin pass:? -passout pass:? -key ? -in ? -out ? -days ? -config ? ", $ssl_password, $ssl_password, $openssl_cmd_key_file2, $openssl_cmd_csr_file, $openssl_cmd_crt_file, $ssl_days, $config_file);
 					$app->log("Creating self-signed SSL Cert for: $domain", LOGLEVEL_DEBUG);
 				};
 
@@ -402,7 +404,8 @@ class apache2_plugin {
 		if($data["new"]["ssl_action"] == 'save') {
 			$tmp = array();
 			$crt_data = '';
-			exec('openssl x509 -noout -text -in '.escapeshellarg($crt_file),$tmp);
+			$app->system->exec_safe('openssl x509 -noout -text -in ?', $crt_file);
+			$tmp = $app->system->last_exec_out();
 			$crt_data = implode("\n",$tmp);
 			if(stristr($crt_data,'.acme.invalid')) {
 				$data["new"]["ssl_action"] = '';
diff --git a/server/plugins-available/apps_vhost_plugin.inc.php b/server/plugins-available/apps_vhost_plugin.inc.php
index b843e3c8a4..41e3cdd82c 100644
--- a/server/plugins-available/apps_vhost_plugin.inc.php
+++ b/server/plugins-available/apps_vhost_plugin.inc.php
@@ -156,11 +156,11 @@ class apps_vhost_plugin {
 				$apps_vhost_ip = $web_config['apps_vhost_ip'].':';
 			}
 
-			$socket_dir = escapeshellcmd($web_config['php_fpm_socket_dir']);
+			$socket_dir = $web_config['php_fpm_socket_dir'];
 			if(substr($socket_dir, -1) != '/') $socket_dir .= '/';
-			if(!is_dir($socket_dir)) exec('mkdir -p '.$socket_dir);
+			if(!is_dir($socket_dir)) $app->system->exec_safe('mkdir -p ?', $socket_dir);
 			$fpm_socket = $socket_dir.'apps.sock';
-			$cgi_socket = escapeshellcmd($web_config['nginx_cgi_socket']);
+			$cgi_socket = $web_config['nginx_cgi_socket'];
 
 			$content = str_replace('{apps_vhost_ip}', $apps_vhost_ip, $content);
 			$content = str_replace('{apps_vhost_port}', $web_config['apps_vhost_port'], $content);
-- 
GitLab