From d87f76019fc231ec20d95126a7fee0487e7be5f0 Mon Sep 17 00:00:00 2001 From: tbrehm <t.brehm@ispconfig.org> Date: Tue, 14 Aug 2012 10:56:20 -0400 Subject: [PATCH] - Added new web folder named private to web folder layout. The folder is intended to store data that shall not be visible in the web directory, it is owned by the user of the web. - Changed ownership of web root directory to root user in all security modes to prevent symlink attacks. - Apache log files are now owned by user root. - Improved functions in system library. --- server/lib/classes/system.inc.php | 329 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 297 insertions(+), 32 deletions(-) diff --git a/server/lib/classes/system.inc.php b/server/lib/classes/system.inc.php index 685d4f2..14b9413 100644 --- a/server/lib/classes/system.inc.php +++ b/server/lib/classes/system.inc.php @@ -610,18 +610,122 @@ * Edit the owner of a file * */ - function chown($file, $owner, $group = ''){ - $owner_change = @chown($file, $owner); - if($group != ''){ - $group_change = @chgrp($file, $group); - } else { - $group_change = 1; + function chown($file, $owner, $allow_symlink = false){ + global $app; + if($allow_symlink == false && $this->checkpath($file) == false) { + $app->log("Action aborted, file is a symlink: $file",LOGLEVEL_WARN); + return false; } - if($owner_change && $group_change){ - return true; - } else { - return false; + if(file_exists($file)) { + if(@chown($file, $owner)) { + return true; + } else { + $app->log("chown failed: $file : $owner",LOGLEVEL_DEBUG); + return false; + } } + } + + function chgrp($file, $group = '', $allow_symlink = false){ + global $app; + if($allow_symlink == false && $this->checkpath($file) == false) { + $app->log("Action aborted, file is a symlink: $file",LOGLEVEL_WARN); + return false; + } + if(file_exists($file)) { + if(@chgrp($file, $group)) { + return true; + } else { + $app->log("chgrp failed: $file : $group",LOGLEVEL_DEBUG); + return false; + } + } + } + + //* Change the mode of a file + function chmod($file, $mode, $allow_symlink = false) { + global $app; + if($allow_symlink == false && $this->checkpath($file) == false) { + $app->log("Action aborted, file is a symlink: $file",LOGLEVEL_WARN); + return false; + } + if(@chmod($file, $mode)) { + return true; + } else { + $app->log("chmod failed: $file : $mode",LOGLEVEL_DEBUG); + return false; + } + } + + function file_put_contents($filename, $data, $allow_symlink = false) { + global $app; + if($allow_symlink == false && $this->checkpath($filename) == false) { + $app->log("Action aborted, file is a symlink: $filename",LOGLEVEL_WARN); + return false; + } + if(file_exists($filename)) unlink($filename); + return file_put_contents($filename, $data); + } + + function file_get_contents($filename, $allow_symlink = false) { + global $app; + if($allow_symlink == false && $this->checkpath($filename) == false) { + $app->log("Action aborted, file is a symlink: $filename",LOGLEVEL_WARN); + return false; + } + return file_put_contents($filename, $data); + } + + function rename($filename, $new_filename, $allow_symlink = false) { + global $app; + if($allow_symlink == false && $this->checkpath($filename) == false) { + $app->log("Action aborted, file is a symlink: $filename",LOGLEVEL_WARN); + return false; + } + return rename($filename, $new_filename); + } + + function mkdir($dirname, $allow_symlink = false) { + global $app; + if($allow_symlink == false && $this->checkpath($dirname) == false) { + $app->log("Action aborted, file is a symlink: $dirname",LOGLEVEL_WARN); + return false; + } + if(@mkdir($dirname)) { + return true; + } else { + $app->log("mkdir failed: $dirname",LOGLEVEL_DEBUG); + return false; + } + } + + function unlink($file) { + if(file_exists($filename)) { + return unlink($filename); + } + } + + function copy($file1,$file2) { + return copy($file1,$file2); + } + + function checkpath($path) { + $path = trim($path); + //* We allow only absolute paths + if(substr($path,0,1) != '/') return false; + + //* We allow only some characters in the path + if(!preg_match('/[a-zA-Z0-9_\.\-]{1,}/',$path)) return false; + + //* Check path for symlinks + $path_parts = explode('/',$path); + $testpath = ''; + foreach($path_parts as $p) { + $testpath .= '/'.$p; + if(is_link($testpath)) return false; + } + + return true; } /** @@ -653,6 +757,7 @@ } } + /* function usermod($user, $groups){ global $app; if($this->is_user($user)){ @@ -692,6 +797,7 @@ return false; } } + */ /**boot autostart etc * @@ -926,7 +1032,7 @@ * */ function network_info(){ - $dist = $this->server_conf["dist"]; + $dist = $this->server_conf['dist']; ob_start(); passthru('ifconfig'); $output = ob_get_contents(); @@ -950,9 +1056,9 @@ } $output = trim(ob_get_contents()); ob_end_clean(); - if($output != ""){ - $ifconfig["INTERFACE"][$interface] = $output; - $ifconfig["IP"][$output] = $interface; + if($output != ''){ + $ifconfig['INTERFACE'][$interface] = $output; + $ifconfig['IP'][$output] = $interface; } } if(!empty($ifconfig)){ @@ -1087,23 +1193,23 @@ if ($urlHandle){ socket_set_timeout($urlHandle, $timeout); - $urlString = 'GET '.$path." HTTP/1.0\r\nHost: ".$url_parts["host"]."\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n"; + $urlString = 'GET '.$path." HTTP/1.0\r\nHost: ".$url_parts['host']."\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n"; if ($user) $urlString .= 'Authorization: Basic '.base64_encode($user.':'.$pass)."\r\n"; $urlString .= "\r\n"; fputs($urlHandle, $urlString); - $month["Jan"] = '01'; - $month["Feb"] = '02'; - $month["Mar"] = '03'; - $month["Apr"] = '04'; - $month["May"] = '05'; - $month["Jun"] = '06'; - $month["Jul"] = '07'; - $month["Aug"] = '08'; - $month["Sep"] = '09'; - $month["Oct"] = '10'; - $month["Nov"] = '11'; - $month["Dec"] = '12'; + $month['Jan'] = '01'; + $month['Feb'] = '02'; + $month['Mar'] = '03'; + $month['Apr'] = '04'; + $month['May'] = '05'; + $month['Jun'] = '06'; + $month['Jul'] = '07'; + $month['Aug'] = '08'; + $month['Sep'] = '09'; + $month['Oct'] = '10'; + $month['Nov'] = '11'; + $month['Dec'] = '12'; $c = 0; $l = 0; $startzeit = time(); @@ -1132,6 +1238,11 @@ } function replaceLine($filename,$search_pattern,$new_line,$strict = 0,$append = 1) { + global $app; + if($this->checkpath($filename) == false) { + $app->log("Action aborted, file is a symlink: $filename",LOGLEVEL_WARN); + return false; + } $lines = @file($filename); $out = ''; $found = 0; @@ -1157,14 +1268,21 @@ if($found == 0) { //* add \n if the last line does not end with \n or \r - if(substr($out,-1) != "\n" && substr($out,-1) != "\r") $out .= "\n"; + if(substr($out,-1) != "\n" && substr($out,-1) != "\r" && filesize($filename) > 0) $out .= "\n"; //* add the new line at the end of the file - if($append == 1) $out .= $new_line."\n"; + if($append == 1) { + $out .= $new_line."\n"; + } } file_put_contents($filename,$out); } function removeLine($filename,$search_pattern,$strict = 0) { + global $app; + if($this->checkpath($filename) == false) { + $app->log("Action aborted, file is a symlink: $filename",LOGLEVEL_WARN); + return false; + } if($lines = @file($filename)) { $out = ''; foreach($lines as $line) { @@ -1191,13 +1309,15 @@ } else { $dir = escapeshellcmd($maildir_path); } + + if(!is_dir($dir)) mkdir($dir, 0700, true); if($user != '' && $user != 'root' && $this->is_user($user)) { $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; - chown($dir,$user); - chgrp($dir,$group); + if(is_dir($dir)) $this->chown($dir,$user); + if(is_dir($dir)) $this->chgrp($dir,$group); $chown_mdsub = true; } @@ -1205,7 +1325,7 @@ $maildirsubs = array('cur','new','tmp'); foreach ($maildirsubs as $mdsub) { - mkdir($dir.'/'.$mdsub, 0700, true); + if(!is_dir($dir.'/'.$mdsub)) mkdir($dir.'/'.$mdsub, 0700, true); if ($chown_mdsub) { chown($dir.'/'.$mdsub, $user); chgrp($dir.'/'.$mdsub, $group); @@ -1249,6 +1369,151 @@ $app->log('Created Maildir '.$maildir_path.' with subfolder: '.$subfolder,LOGLEVEL_DEBUG); } + + //* Function to create directory paths and chown them to a user and group + function mkdirpath($path, $mode = 0755, $user = '', $group = '') { + $path_parts = explode('/',$path); + $new_path = ''; + if(is_array($path_parts)) { + foreach($path_parts as $part) { + $new_path .= '/'.$part; + if(!@is_dir($new_path)) { + $this->mkdir($new_path); + $this->chmod($new_path,$mode); + if($user != '') $this->chown($new_path,$user); + if($group != '') $this->chgrp($new_path,$group); + } + } + } + + } + + //* Check if a application is installed + function is_installed($appname) { + exec('which '.escapeshellcmd($appname).' 2> /dev/null',$out,$returncode); + if(isset($out[0]) && stristr($out[0],$appname) && $returncode == 0) { + return true; + } else { + return false; + } + } + + function web_folder_protection($document_root,$protect) { + global $app,$conf; + + if($this->checkpath($document_root) == false) { + $app->log("Action aborted, target is a symlink: $document_root",LOGLEVEL_DEBUG); + return false; + } + + //* load the server configuration options + $app->uses('getconf'); + $web_config = $app->getconf->get_server_config($conf['server_id'], 'web'); + + 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)); + } else { + //* Remove protection + if($document_root != '' && $document_root != '/' && strlen($document_root) > 6 && !stristr($document_root,'..')) exec('chattr -i '.escapeshellcmd($document_root)); + } + } + + function usermod($username, $uid = 0, $gid = 0, $home = '', $shell = '', $password = '', $login = '') { + global $app; + + if($login == '') $login = $username; + + //* Change values in /etc/passwd + $passwd_file_array = file('/etc/passwd'); + if(is_array($passwd_file_array)) { + foreach($passwd_file_array as $line) { + $line = trim($line); + $parts = explode(':',$line); + if($parts[0] == $username) { + if(trim($login) != '' && trim($login) != trim($username)) $parts[0] = trim($login); + if(!empty($uid)) $parts[2] = trim($uid); + if(!empty($gid)) $parts[3] = trim($gid); + if(trim($home) != '') $parts[5] = trim($home); + if(trim($shell) != '') $parts[6] = trim($shell); + $new_line = implode(':',$parts); + copy('/etc/passwd','/etc/passwd~'); + chmod('/etc/passwd~',0600); + $app->uses('system'); + $app->system->replaceLine('/etc/passwd',$line,$new_line,1,0); + } + } + unset($passwd_file_array); + } + + //* If username != login, change username in group and gshadow file + if($username != $login) { + $group_file_array = file('/etc/group'); + if(is_array($group_file_array)) { + foreach($group_file_array as $line) { + $line = trim($line); + $parts = explode(':',$line); + if(strstr($parts[3],$username)) { + $uparts = explode(',',$parts[3]); + if(is_array($uparts)) { + foreach($uparts as $key => $val) { + if($val == $username) $uparts[$key] = $login; + } + } + $parts[3] = implode(',',$uparts); + $new_line = implode(':',$parts); + copy('/etc/group','/etc/group~'); + chmod('/etc/group~',0600); + $app->system->replaceLine('/etc/group',$line,$new_line,1,0); + } + } + } + unset($group_file_array); + + $gshadow_file_array = file('/etc/gshadow'); + if(is_array($gshadow_file_array)) { + foreach($gshadow_file_array as $line) { + $line = trim($line); + $parts = explode(':',$line); + if(strstr($parts[3],$username)) { + $uparts = explode(',',$parts[3]); + if(is_array($uparts)) { + foreach($uparts as $key => $val) { + if($val == $username) $uparts[$key] = $login; + } + } + $parts[3] = implode(',',$uparts); + $new_line = implode(':',$parts); + copy('/etc/gshadow','/etc/gshadow~'); + chmod('/etc/gshadow~',0600); + $app->system->replaceLine('/etc/gshadow',$line,$new_line,1,0); + } + } + } + unset($group_file_array); + } + + + //* When password or login name has been changed + if($password != '' || $username != $login) { + $shadow_file_array = file('/etc/shadow'); + if(is_array($shadow_file_array)) { + foreach($shadow_file_array as $line) { + $line = trim($line); + $parts = explode(':',$line); + if($parts[0] == $username) { + if(trim($login) != '' && trim($login) != trim($username)) $parts[0] = trim($login); + if(trim($password) != '') $parts[1] = trim($password); + $new_line = implode(':',$parts); + copy('/etc/shadow','/etc/shadow~'); + chmod('/etc/shadow~',0600); + $app->system->replaceLine('/etc/shadow',$line,$new_line,1,0); + } + } + } + unset($shadow_file_array); + } + } } ?> -- Gitblit v1.9.1