Skip to content
Snippets Groups Projects
Commit 333d13a9 authored by Marius Burkard's avatar Marius Burkard
Browse files

Merge branch 'develop' into 'develop'

Merge ssh user security enhancement

Closes #5920

See merge request ispconfig/ispconfig3!1333
parents f058c7ef 9f43b350
No related branches found
Tags 3.2.1
No related merge requests found
......@@ -61,7 +61,7 @@ class functions {
if(is_string($to) && strpos($to, ',') !== false) {
$to = preg_split('/\s*,\s*/', $to);
}
$app->ispcmail->send($to);
$app->ispcmail->finish();
......@@ -234,7 +234,7 @@ class functions {
if(preg_match($regex, $result['ip'])) $ips[] = $result['ip'];
}
}
$results = $app->db->queryAllRecords("SELECT remote_ips FROM web_database WHERE remote_ips != ''");
if(!empty($results) && is_array($results)){
foreach($results as $result){
......@@ -290,6 +290,34 @@ class functions {
return round(pow(1024, $base-floor($base)), $precision).$suffixes[floor($base)];
}
/**
* Normalize a path and strip duplicate slashes from it
*
* This will also remove all /../ from the path, reducing the preceding path elements
*
* @param string $path
* @return string
*/
public function normalize_path($path) {
$path = preg_replace('~[/]{2,}~', '/', $path);
$parts = explode('/', $path);
$return_parts = array();
foreach($parts as $current_part) {
if($current_part === '..') {
if(!empty($return_parts) && end($return_parts) !== '') {
array_pop($return_parts);
}
} else {
$return_parts[] = $current_part;
}
}
return implode('/', $return_parts);
}
/** IDN converter wrapper.
* all converter classes should be placed in ISPC_CLASS_PATH.'/idn/'
*/
......@@ -370,42 +398,42 @@ class functions {
public function is_allowed_user($username, $restrict_names = false) {
global $app;
$name_blacklist = array('root','ispconfig','vmail','getmail');
if(in_array($username,$name_blacklist)) return false;
if(preg_match('/^[a-zA-Z0-9\.\-_]{1,32}$/', $username) == false) return false;
if($restrict_names == true && preg_match('/^web\d+$/', $username) == false) return false;
return true;
}
public function is_allowed_group($groupname, $restrict_names = false) {
global $app;
$name_blacklist = array('root','ispconfig','vmail','getmail');
if(in_array($groupname,$name_blacklist)) return false;
if(preg_match('/^[a-zA-Z0-9\.\-_]{1,32}$/', $groupname) == false) return false;
if($restrict_names == true && preg_match('/^client\d+$/', $groupname) == false) return false;
return true;
}
public function getimagesizefromstring($string){
if (!function_exists('getimagesizefromstring')) {
$uri = 'data://application/octet-stream;base64,' . base64_encode($string);
return getimagesize($uri);
} else {
return getimagesizefromstring($string);
}
}
}
public function password($minLength = 10, $special = false){
global $app;
$iteration = 0;
$password = "";
$maxLength = $minLength + 5;
......@@ -430,7 +458,7 @@ class functions {
public function getRandomInt($min, $max){
return floor((mt_rand() / mt_getrandmax()) * ($max - $min + 1)) + $min;
}
public function generate_customer_no(){
global $app;
// generate customer no.
......@@ -438,13 +466,13 @@ class functions {
while($app->db->queryOneRecord("SELECT client_id FROM client WHERE customer_no = ?", $customer_no)) {
$customer_no = mt_rand(100000, 999999);
}
return $customer_no;
}
public function generate_ssh_key($client_id, $username = ''){
global $app;
// generate the SSH key pair for the client
$id_rsa_file = '/tmp/'.uniqid('',true);
$id_rsa_pub_file = $id_rsa_file.'.pub';
......@@ -458,7 +486,7 @@ class functions {
$app->log("Failed to create SSH keypair for ".$username, LOGLEVEL_WARN);
}
}
public function htmlentities($value) {
global $conf;
......@@ -474,10 +502,10 @@ class functions {
} else {
$out = htmlentities($value, ENT_QUOTES, $conf["html_content_encoding"]);
}
return $out;
}
// Function to check paths before we use it as include. Use with absolute paths only.
public function check_include_path($path) {
if(strpos($path,'//') !== false) die('Include path seems to be an URL: '.$this->htmlentities($path));
......@@ -488,7 +516,7 @@ class functions {
if(substr($path,0,strlen(ISPC_ROOT_PATH)) != ISPC_ROOT_PATH) die('Path '.$this->htmlentities($path).' is outside of ISPConfig installation directory.');
return $path;
}
// Function to check language strings
public function check_language($language) {
global $app;
......@@ -496,10 +524,10 @@ class functions {
return $language;
} else {
$app->log('Wrong language string: '.$this->htmlentities($language),1);
return 'en';
return 'en';
}
}
}
?>
......@@ -399,7 +399,7 @@ class tform_base {
$tmp_key = $limit_parts[2];
$allowed = $allowed = explode(',',$tmp_conf[$tmp_key]);
}
if($formtype == 'CHECKBOX') {
if(strstr($limit,'force_')) {
// Force the checkbox field to be ticked and enabled
......@@ -958,6 +958,9 @@ class tform_base {
case 'STRIPNL':
$returnval = str_replace(array("\n","\r"),'', $returnval);
break;
case 'NORMALIZEPATH':
$returnval = $app->functions->normalize_path($returnval);
break;
default:
$this->errorMessage .= "Unknown Filter: ".$filter['type'];
break;
......
......@@ -232,6 +232,12 @@ if($_SESSION["s"]["user"]["typ"] == 'admin') {
'dir' => array (
'datatype' => 'VARCHAR',
'formtype' => 'TEXT',
'filters' => array(
0 => array (
'event' => 'SAVE',
'type' => 'NORMALIZEPATH'
)
),
'validators' => array ( 0 => array ( 'type' => 'NOTEMPTY',
'errmsg'=> 'directory_error_empty'),
1 => array ( 'type' => 'REGEX',
......
......@@ -356,6 +356,34 @@ class functions {
}
}
/**
* Normalize a path and strip duplicate slashes from it
*
* This will also remove all /../ from the path, reducing the preceding path elements
*
* @param string $path
* @return string
*/
public function normalize_path($path) {
$path = preg_replace('~[/]{2,}~', '/', $path);
$parts = explode('/', $path);
$return_parts = array();
foreach($parts as $current_part) {
if($current_part === '..') {
if(!empty($return_parts) && end($return_parts) !== '') {
array_pop($return_parts);
}
} else {
$return_parts[] = $current_part;
}
}
return implode('/', $return_parts);
}
/** IDN converter wrapper.
* all converter classes should be placed in ISPC_CLASS_PATH.'/idn/'
*/
......@@ -435,10 +463,10 @@ class functions {
}
return implode("\n", $domains);
}
public function generate_ssh_key($client_id, $username = ''){
global $app;
// generate the SSH key pair for the client
$id_rsa_file = '/tmp/'.uniqid('',true);
$id_rsa_pub_file = $id_rsa_file.'.pub';
......
......@@ -2300,6 +2300,36 @@ class system{
return true;
}
public function is_allowed_path($path) {
global $app;
$path = $app->functions->normalize_path($path);
if(file_exists($path)) {
$path = realpath($path);
}
$blacklisted_paths_regex = array(
'@^/$@',
'@^/proc(/.*)?$@',
'@^/sys(/.*)?$@',
'@^/etc(/.*)?$@',
'@^/dev(/.*)?$@',
'@^/tmp(/.*)?$@',
'@^/run(/.*)?$@',
'@^/boot(/.*)?$@',
'@^/root(/.*)?$@',
'@^/var(/?|/backups?(/.*)?)?$@',
);
foreach($blacklisted_paths_regex as $regex) {
if(preg_match($regex, $path)) {
return false;
}
}
return true;
}
public function last_exec_out() {
return $this->_last_exec_out;
}
......@@ -2350,6 +2380,8 @@ class system{
}
public function create_jailkit_user($username, $home_dir, $user_home_dir, $shell = '/bin/bash', $p_user = null, $p_user_home_dir = null) {
global $app;
// Disallow operating on root directory
if(realpath($home_dir) == '/') {
$app->log("create_jailkit_user: invalid home_dir: $home_dir", LOGLEVEL_WARN);
......@@ -2379,6 +2411,8 @@ class system{
}
public function create_jailkit_chroot($home_dir, $app_sections = array(), $options = array()) {
global $app;
// Disallow operating on root directory
if(realpath($home_dir) == '/') {
$app->log("create_jailkit_chroot: invalid home_dir: $home_dir", LOGLEVEL_WARN);
......@@ -2450,6 +2484,8 @@ class system{
}
public function create_jailkit_programs($home_dir, $programs = array(), $options = array()) {
global $app;
// Disallow operating on root directory
if(realpath($home_dir) == '/') {
$app->log("create_jailkit_programs: invalid home_dir: $home_dir", LOGLEVEL_WARN);
......
......@@ -96,6 +96,14 @@ class shelluser_base_plugin {
return false;
}
if(is_file($data['new']['dir']) || is_link($data['new']['dir'])) {
$app->log('Shell user dir must not be existing file or symlink.', LOGLEVEL_WARN);
return false;
} elseif(!$app->system->is_allowed_path($data['new']['dir'])) {
$app->log('Shell user dir is not an allowed path: ' . $data['new']['dir'], LOGLEVEL_WARN);
return false;
}
if($data['new']['active'] != 'y' || $data['new']['chroot'] == "jailkit") $data['new']['shell'] = '/bin/false';
if($app->system->is_user($data['new']['puser'])) {
......@@ -207,6 +215,14 @@ class shelluser_base_plugin {
return false;
}
if(is_file($data['new']['dir']) || is_link($data['new']['dir'])) {
$app->log('Shell user dir must not be existing file or symlink.', LOGLEVEL_WARN);
return false;
} elseif(!$app->system->is_allowed_path($data['new']['dir'])) {
$app->log('Shell user dir is not an allowed path: ' . $data['new']['dir'], LOGLEVEL_WARN);
return false;
}
if($data['new']['active'] != 'y') $data['new']['shell'] = '/bin/false';
if($app->system->is_user($data['new']['puser'])) {
......@@ -304,6 +320,14 @@ class shelluser_base_plugin {
return false;
}
if(is_file($data['old']['dir']) || is_link($data['old']['dir'])) {
$app->log('Shell user dir must not be existing file or symlink.', LOGLEVEL_WARN);
return false;
} elseif(!$app->system->is_allowed_path($data['old']['dir'])) {
$app->log('Shell user dir is not an allowed path: ' . $data['old']['dir'], LOGLEVEL_WARN);
return false;
}
if($app->system->is_user($data['old']['username'])) {
// Get the UID of the user
$userid = intval($app->system->getuid($data['old']['username']));
......
......@@ -89,6 +89,15 @@ class shelluser_jailkit_plugin {
return false;
}
if(is_file($data['new']['dir']) || is_link($data['new']['dir'])) {
$app->log('Shell user dir must not be existing file or symlink.', LOGLEVEL_WARN);
return false;
} elseif(!$app->system->is_allowed_path($data['new']['dir'])) {
$app->log('Shell user dir is not an allowed path: ' . $data['new']['dir'], LOGLEVEL_WARN);
return false;
}
if($app->system->is_user($data['new']['puser'])) {
// Get the UID of the parent user
$uid = intval($app->system->getuid($data['new']['puser']));
......@@ -170,6 +179,14 @@ class shelluser_jailkit_plugin {
return false;
}
if(is_file($data['new']['dir']) || is_link($data['new']['dir'])) {
$app->log('Shell user dir must not be existing file or symlink.', LOGLEVEL_WARN);
return false;
} elseif(!$app->system->is_allowed_path($data['new']['dir'])) {
$app->log('Shell user dir is not an allowed path: ' . $data['new']['dir'], LOGLEVEL_WARN);
return false;
}
if($app->system->is_user($data['new']['puser'])) {
$web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ?", $data['new']['parent_domain_id']);
......@@ -241,6 +258,14 @@ class shelluser_jailkit_plugin {
return false;
}
if(is_file($data['old']['dir']) || is_link($data['old']['dir'])) {
$app->log('Shell user dir must not be existing file or symlink.', LOGLEVEL_WARN);
return false;
} elseif(!$app->system->is_allowed_path($data['old']['dir'])) {
$app->log('Shell user dir is not an allowed path: ' . $data['old']['dir'], LOGLEVEL_WARN);
return false;
}
if ($data['old']['chroot'] == "jailkit")
{
$web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ?", $data['old']['parent_domain_id']);
......@@ -518,6 +543,9 @@ class shelluser_jailkit_plugin {
}
//* Get the keys
$existing_keys = file($sshkeys, FILE_IGNORE_NEW_LINES);
if(!$existing_keys) {
$existing_keys = array();
}
$new_keys = explode("\n", $sshrsa);
$old_keys = explode("\n", $this->data['old']['ssh_rsa']);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment