From 393ca8c757481e73cc2af00d69cd6266de311bd6 Mon Sep 17 00:00:00 2001 From: tbrehm <t.brehm@ispconfig.org> Date: Mon, 23 Jul 2012 04:11:19 -0400 Subject: [PATCH] Fixed: FS#2325 - httpd log directory permissions allow symlink attacks. --- server/plugins-available/apache2_plugin.inc.php | 127 +++++++++++++++++++++++++++++++++++++---- 1 files changed, 113 insertions(+), 14 deletions(-) diff --git a/server/plugins-available/apache2_plugin.inc.php b/server/plugins-available/apache2_plugin.inc.php index 30eecfe..54f6cec 100644 --- a/server/plugins-available/apache2_plugin.inc.php +++ b/server/plugins-available/apache2_plugin.inc.php @@ -35,6 +35,7 @@ // private variables var $action = ''; + var $ssl_certificate_changed = false; //* This function is called during ispconfig installation to determine // if a symlink shall be created for this plugin. @@ -85,6 +86,8 @@ $app->plugins->registerEvent('web_folder_update',$this->plugin_name,'web_folder_update'); $app->plugins->registerEvent('web_folder_delete',$this->plugin_name,'web_folder_delete'); + $app->plugins->registerEvent('ftp_user_delete',$this->plugin_name,'ftp_user_delete'); + } // Handle the creation of SSL certificates @@ -110,6 +113,8 @@ //* Create a SSL Certificate if($data['new']['ssl_action'] == 'create') { + + $this->ssl_certificate_changed = true; //* Rename files if they exist if(file_exists($key_file)) rename($key_file,$key_file.'.bak'); @@ -196,16 +201,30 @@ //* Save a SSL certificate to disk if($data["new"]["ssl_action"] == 'save') { + $this->ssl_certificate_changed = true; $ssl_dir = $data["new"]["document_root"]."/ssl"; $domain = ($data["new"]["ssl_domain"] != '')?$data["new"]["ssl_domain"]:$data["new"]["domain"]; + $key_file = $ssl_dir.'/'.$domain.'.key.org'; + $key_file2 = $ssl_dir.'/'.$domain.'.key'; $csr_file = $ssl_dir.'/'.$domain.".csr"; $crt_file = $ssl_dir.'/'.$domain.".crt"; $bundle_file = $ssl_dir.'/'.$domain.".bundle"; + + //* Backup files + if(file_exists($key_file)) copy($key_file,$key_file.'~'); + if(file_exists($key_file2)) copy($key_file2,$key_file2.'~'); + if(file_exists($csr_file)) copy($csr_file,$csr_file.'~'); + if(file_exists($crt_file)) copy($crt_file,$crt_file.'~'); + if(file_exists($bundle_file)) copy($bundle_file,$bundle_file.'~'); + + //* Write new ssl files if(trim($data["new"]["ssl_request"]) != '') file_put_contents($csr_file,$data["new"]["ssl_request"]); if(trim($data["new"]["ssl_cert"]) != '') file_put_contents($crt_file,$data["new"]["ssl_cert"]); if(trim($data["new"]["ssl_bundle"]) != '') file_put_contents($bundle_file,$data["new"]["ssl_bundle"]); + /* Update the DB of the (local) Server */ $app->db->query("UPDATE web_domain SET ssl_action = '' WHERE domain = '".$data['new']['domain']."'"); + /* Update also the master-DB of the Server-Farm */ $app->dbmaster->query("UPDATE web_domain SET ssl_action = '' WHERE domain = '".$data['new']['domain']."'"); $app->log('Saving SSL Cert for: '.$domain,LOGLEVEL_DEBUG); @@ -364,6 +383,9 @@ } } } + + //* Remove protection of old folders + $app->system->web_folder_protection($data['old']['document_root'],false); //* Move the site data $tmp_docroot = explode('/',$data['new']['document_root']); @@ -412,7 +434,7 @@ if(!is_dir($data['new']['document_root'].'/ssl')) exec('mkdir -p '.$data['new']['document_root'].'/ssl'); if(!is_dir($data['new']['document_root'].'/cgi-bin')) exec('mkdir -p '.$data['new']['document_root'].'/cgi-bin'); if(!is_dir($data['new']['document_root'].'/tmp')) exec('mkdir -p '.$data['new']['document_root'].'/tmp'); - + // Remove the symlink for the site, if site is renamed if($this->action == 'update' && $data['old']['domain'] != '' && $data['new']['domain'] != $data['old']['domain']) { if(is_dir('/var/log/ispconfig/httpd/'.$data['old']['domain'])) exec('rm -rf /var/log/ispconfig/httpd/'.$data['old']['domain']); @@ -591,15 +613,17 @@ } } - - //* If the security level is set to high if(($this->action == 'insert' && $data['new']['type'] == 'vhost') or ($web_config['set_folder_permissions_on_update'] == 'y' && $data['new']['type'] == 'vhost')) { + + $app->system->web_folder_protection($data['new']['document_root'],false); + if($web_config['security_level'] == 20) { $this->_exec('chmod 751 '.escapeshellcmd($data['new']['document_root'])); $this->_exec('chmod 751 '.escapeshellcmd($data['new']['document_root']).'/*'); $this->_exec('chmod 710 '.escapeshellcmd($data['new']['document_root'].'/web')); + $this->_exec('chmod 755 '.escapeshellcmd($data['new']['document_root'].'/ssl')); // make tmp directory writable for Apache and the website users $this->_exec('chmod 777 '.escapeshellcmd($data['new']['document_root'].'/tmp')); @@ -632,8 +656,9 @@ //* Chown all default directories $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'])); $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/cgi-bin')); - $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/log')); - $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/ssl')); + // $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/log')); + $this->_exec('chown root:'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/log')); + $this->_exec('chown root:root '.escapeshellcmd($data['new']['document_root'].'/ssl')); $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/tmp')); $this->_exec('chown -R '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/web')); @@ -642,9 +667,10 @@ * website root has to be owned by the root user and we have to chmod it to 755 then */ - //* Check if there is a jailkit user for this site + //* Check if there is a jailkit user or cronjob for this site $tmp = $app->db->queryOneRecord('SELECT count(shell_user_id) as number FROM shell_user WHERE parent_domain_id = '.$data['new']['domain_id']." AND chroot = 'jailkit'"); - if($tmp['number'] > 0) { + $tmp2 = $app->db->queryOneRecord('SELECT count(id) as number FROM cron WHERE parent_domain_id = '.$data['new']['domain_id']." AND `type` = 'chrooted'"); + if($tmp['number'] > 0 || $tmp2['number'] > 0) { $this->_exec('chmod 755 '.escapeshellcmd($data['new']['document_root'])); $this->_exec('chown root:root '.escapeshellcmd($data['new']['document_root'])); } @@ -666,19 +692,28 @@ $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/cgi-bin')); $this->_exec('chown root:root '.escapeshellcmd($data['new']['document_root'].'/log')); $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/tmp')); - $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/ssl')); + $this->_exec('chown root:root '.escapeshellcmd($data['new']['document_root'].'/ssl')); $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root'].'/web')); } } + + //* Protect web folders + $app->system->web_folder_protection($data['new']['document_root'],true); // Change the ownership of the error log to the owner of the website if(!@is_file($data['new']['document_root'].'/log/error.log')) exec('touch '.escapeshellcmd($data['new']['document_root']).'/log/error.log'); $this->_exec('chown '.$username.':'.$groupname.' '.escapeshellcmd($data['new']['document_root']).'/log/error.log'); - //* Write the custom php.ini file, if custom_php_ini filed is not empty + //* Write the custom php.ini file, if custom_php_ini fieled is not empty $custom_php_ini_dir = $web_config['website_basedir'].'/conf/'.$data['new']['system_user']; if(!is_dir($web_config['website_basedir'].'/conf')) mkdir($web_config['website_basedir'].'/conf'); + + //* add open_basedir restriction to custom php.ini content, required for suphp only + if(!stristr($data['new']['custom_php_ini'],'open_basedir') && $data['new']['php'] == 'suphp') { + $data['new']['custom_php_ini'] .= "\nopen_basedir = '".$data['new']['php_open_basedir']."'\n"; + } + //* Create custom php.ini if(trim($data['new']['custom_php_ini']) != '') { $has_custom_php_ini = true; if(!is_dir($custom_php_ini_dir)) mkdir($custom_php_ini_dir); @@ -1102,6 +1137,11 @@ } //* Add vhost for ipv4 IP with SSL + $ssl_dir = $data['new']['document_root'].'/ssl'; + $domain = $data['new']['ssl_domain']; + $key_file = $ssl_dir.'/'.$domain.'.key'; + $crt_file = $ssl_dir.'/'.$domain.'.crt'; + if($data['new']['ssl_domain'] != '' && $data['new']['ssl'] == 'y' && @is_file($crt_file) && @is_file($key_file) && (@filesize($crt_file)>0) && (@filesize($key_file)>0)) { if(count($rewrite_rules) > 0){ $vhosts[] = array('ip_address' => $data['new']['ip_address'], 'ssl_enabled' => 1, 'port' => '443', 'redirects' => $rewrite_rules); @@ -1208,7 +1248,9 @@ if(!is_file($data['new']['document_root'].'/.htpasswd_stats') || $data['new']['stats_password'] != $data['old']['stats_password']) { if(trim($data['new']['stats_password']) != '') { $htp_file = 'admin:'.trim($data['new']['stats_password']); + $app->system->web_folder_protection($data['new']['document_root'],false); file_put_contents($data['new']['document_root'].'/.htpasswd_stats',$htp_file); + $app->system->web_folder_protection($data['new']['document_root'],true); chmod($data['new']['document_root'].'/.htpasswd_stats',0755); unset($htp_file); } @@ -1244,6 +1286,33 @@ //* There is no backup file, so we create a empty vhost file with a warning message inside file_put_contents($vhost_file,"# Apache did not start after modifying this vhost file.\n# Please check file $vhost_file.err for syntax errors."); } + if($this->ssl_certificate_changed === true) { + + $ssl_dir = $data['new']['document_root'].'/ssl'; + $domain = $data['new']['ssl_domain']; + $key_file = $ssl_dir.'/'.$domain.'.key.org'; + $key_file2 = $ssl_dir.'/'.$domain.'.key'; + $csr_file = $ssl_dir.'/'.$domain.'.csr'; + $crt_file = $ssl_dir.'/'.$domain.'.crt'; + $bundle_file = $ssl_dir.'/'.$domain.'.bundle'; + + //* Backup the files that might have caused the error + if(is_file($key_file)) copy($key_file,$key_file.'.err'); + if(is_file($key_file2)) copy($key_file2,$key_file2.'.err'); + if(is_file($csr_file)) copy($csr_file,$csr_file.'.err'); + if(is_file($crt_file)) copy($crt_file,$crt_file.'.err'); + if(is_file($bundle_file)) copy($bundle_file,$bundle_file.'.err'); + + //* Restore the ~ backup files + if(is_file($key_file.'~')) copy($key_file.'~',$key_file); + if(is_file($key_file2.'~')) copy($key_file2.'~',$key_file2); + if(is_file($crt_file.'~')) copy($crt_file.'~',$crt_file); + if(is_file($csr_file.'~')) copy($csr_file.'~',$csr_file); + if(is_file($bundle_file.'~')) copy($bundle_file.'~',$bundle_file); + + $app->log('Apache did not restart after the configuration change for website '.$data['new']['domain'].' Reverting the SSL configuration. Saved non-working SSL files with .err extension.',LOGLEVEL_WARN); + } + $app->services->restartService('httpd','restart'); } } else { @@ -1256,9 +1325,26 @@ } } + //* The vhost is written and apache has been restarted, so we + // can reset the ssl changed var to false and cleanup some files + $this->ssl_certificate_changed = false; + + $ssl_dir = $data['new']['document_root'].'/ssl'; + $domain = $data['new']['ssl_domain']; + $key_file = $ssl_dir.'/'.$domain.'.key.org'; + $key_file2 = $ssl_dir.'/'.$domain.'.key'; + $csr_file = $ssl_dir.'/'.$domain.'.csr'; + $crt_file = $ssl_dir.'/'.$domain.'.crt'; + $bundle_file = $ssl_dir.'/'.$domain.'.bundle'; + + if(@is_file($key_file.'~')) unlink($key_file.'~'); + if(@is_file($key2_file.'~')) unlink($key2_file.'~'); + if(@is_file($crt_file.'~')) unlink($crt_file.'~'); + if(@is_file($csr_file.'~')) unlink($csr_file.'~'); + if(@is_file($bundle_file.'~')) unlink($bundle_file.'~'); + // Remove the backup copy of the config file. if(@is_file($vhost_file.'~')) unlink($vhost_file.'~'); - //* Unset action to clean it for next processed vhost. $this->action = ''; @@ -1270,7 +1356,10 @@ // load the server configuration options $app->uses('getconf'); + $app->uses('system'); $web_config = $app->getconf->get_server_config($conf['server_id'], 'web'); + + $app->system->web_folder_protection($data['new']['document_root'],false); //* Check if this is a chrooted setup if($web_config['website_basedir'] != '' && @is_file($web_config['website_basedir'].'/etc/passwd')) { @@ -1644,6 +1733,16 @@ } + + public function ftp_user_delete($event_name,$data) { + global $app, $conf; + + $ftpquota_file = $data['old']['dir'].'/.ftpquota'; + if(file_exists($ftpquota_file)) unlink($ftpquota_file); + + } + + /** * This function is called when a Webdav-User is inserted, updated or deleted. @@ -2048,12 +2147,12 @@ $value = escapeshellcmd(trim($value)); $key = escapeshellcmd(trim($key)); switch (strtolower($value)) { - case 'on': - case 'off': - case '1': case '0': // PHP-FPM might complain about invalid boolean value if you use 0 $value = 'off'; + case '1': + case 'on': + case 'off': case 'true': case 'false': case 'yes': @@ -2239,4 +2338,4 @@ } // end class -?> \ No newline at end of file +?> -- Gitblit v1.9.1