Commit b15856da authored by Marius Burkard's avatar Marius Burkard
Browse files

Merge branch 'develop' into 'develop'

Improve API security

See merge request ispconfig/ispconfig3!1533
parents d2b75bd8 99b8b423
Pipeline #9694 passed with stage
in 16 seconds
......@@ -26,7 +26,7 @@ Development branch: [![pipeline status](https://git.ispconfig.org/ispconfig/ispc
[^1]: not actively tested
## Supported operating systems
- Debian 9, 10, and testing
- Debian 9 - 11, and testing
- Ubuntu 16.04 - 20.04
- CentOS 7 and 8
......
ALTER TABLE `remote_session` ADD `remote_ip` VARCHAR(39) NOT NULL DEFAULT '' AFTER `tstamp`;
\ No newline at end of file
......@@ -1317,6 +1317,7 @@ CREATE TABLE `remote_session` (
`remote_functions` text,
`client_login` tinyint(1) unsigned NOT NULL default '0',
`tstamp` int(10) unsigned NOT NULL DEFAULT '0',
`remote_ip` varchar(39) NOT NULL DEFAULT '',
PRIMARY KEY (`remote_session`)
) DEFAULT CHARSET=utf8 ;
......
......@@ -85,10 +85,20 @@ class remoting {
//* Delete old remoting sessions
$sql = "DELETE FROM remote_session WHERE tstamp < UNIX_TIMESTAMP()";
$app->db->query($sql);
//* Check for max. login attempts
$ip_md5 = md5($_SERVER['REMOTE_ADDR']);
$sql = "SELECT * FROM `attempts_login` WHERE `ip`= ? AND `login_time` > (NOW() - INTERVAL 5 MINUTE) LIMIT 1";
$alreadyfailed = $app->db->queryOneRecord($sql, $ip_md5);
if($alreadyfailed['times'] >= 10) {
throw new SoapFault('login_failure_limit', 'The login failure limit has been reached.');
return false;
}
if($client_login == true) {
$sql = "SELECT * FROM sys_user WHERE USERNAME = ?";
$user = $app->db->queryOneRecord($sql, $username);
$user = $app->db->queryOneRecord($sql, (string)$username);
if($user) {
$saved_password = stripslashes($user['passwort']);
......@@ -104,6 +114,16 @@ class remoting {
}
}
} else {
if(!$alreadyfailed['times'] )
{
//* user login the first time wrong
$sql = "INSERT INTO `attempts_login` (`ip`, `times`, `login_time`) VALUES (?, 1, NOW())";
$app->db->query($sql, $ip_md5);
} elseif($alreadyfailed['times'] >= 1) {
//* update times wrong
$sql = "UPDATE `attempts_login` SET `times`=`times`+1, `login_time`=NOW() WHERE `ip` = ? ORDER BY `login_time` DESC LIMIT 1";
$app->db->query($sql, $ip_md5);
}
throw new SoapFault('client_login_failed', 'The login failed. Username or password wrong.');
}
if($user['active'] != 1) {
......@@ -119,17 +139,23 @@ class remoting {
//* Create a remote user session
//srand ((double)microtime()*1000000);
$remote_session = md5(mt_rand().uniqid('ispco'));
$remote_session = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'),0,1).sha1(mt_rand().uniqid('ispco',true));
$remote_userid = $user['userid'];
$remote_functions = '';
$tstamp = time() + $this->session_timeout;
$sql = 'INSERT INTO remote_session (remote_session,remote_userid,remote_functions,client_login,tstamp'
.') VALUES (?, ?, ?, 1, ?)';
$app->db->query($sql, $remote_session,$remote_userid,$remote_functions,$tstamp);
$ip = $_SERVER['REMOTE_ADDR'];
$sql = 'INSERT INTO remote_session (remote_session,remote_userid,remote_functions,client_login,tstamp,remote_ip'
.') VALUES (?, ?, ?, 1, ?, ?)';
$app->db->query($sql, $remote_session,$remote_userid,$remote_functions,$tstamp,$ip);
//* Delete login attempts after successful login
$sql = "DELETE FROM `attempts_login` WHERE `ip`=?";
$app->db->query($sql, $ip_md5);
return $remote_session;
} else {
$sql = "SELECT * FROM remote_user WHERE remote_username = ?";
$remote_user = $app->db->queryOneRecord($sql, $username);
$remote_user = $app->db->queryOneRecord($sql, (string)$username);
if($remote_user) {
if(substr($remote_user['remote_password'], 0, 1) === '$') {
if(crypt(stripslashes($password), $remote_user['remote_password']) != $remote_user['remote_password']) {
......@@ -138,7 +164,7 @@ class remoting {
} elseif(md5($password) == $remote_user['remote_password']) {
// update hash algo
$sql = 'UPDATE `remote_user` SET `remote_password` = ? WHERE `remote_username` = ?';
$app->db->query($sql, $app->auth->crypt_password($password), $username);
$app->db->query($sql, $app->auth->crypt_password($password), (string)$username);
} else {
$remote_user = null;
}
......@@ -185,15 +211,32 @@ class remoting {
}
//* Create a remote user session
//srand ((double)microtime()*1000000);
$remote_session = md5(mt_rand().uniqid('ispco'));
$remote_session = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'),0,1).sha1(mt_rand().uniqid('ispco',true));
$remote_userid = $remote_user['remote_userid'];
$remote_functions = $remote_user['remote_functions'];
$tstamp = time() + $this->session_timeout;
$sql = 'INSERT INTO remote_session (remote_session,remote_userid,remote_functions,tstamp'
.') VALUES (?, ?, ?, ?)';
$app->db->query($sql, $remote_session,$remote_userid,$remote_functions,$tstamp);
$sql = 'INSERT INTO remote_session (remote_session,remote_userid,remote_functions,tstamp,remote_ip'
.') VALUES (?, ?, ?, ?, ?)';
$app->db->query($sql, $remote_session,$remote_userid,$remote_functions,$tstamp, $ip);
//* Delete login attempts after successful login
$sql = "DELETE FROM `attempts_login` WHERE `ip`=?";
$app->db->query($sql, $ip_md5);
return $remote_session;
} else {
if(!$alreadyfailed['times'] )
{
//* user login the first time wrong
$sql = "INSERT INTO `attempts_login` (`ip`, `times`, `login_time`) VALUES (?, 1, NOW())";
$app->db->query($sql, $ip_md5);
} elseif($alreadyfailed['times'] >= 1) {
//* update times wrong
$sql = "UPDATE `attempts_login` SET `times`=`times`+1, `login_time`=NOW() WHERE `ip` = ? ORDER BY `login_time` DESC LIMIT 1";
$app->db->query($sql, $ip_md5);
}
throw new SoapFault('login_failed', 'The login failed. Username or password wrong.');
return false;
}
......@@ -212,7 +255,7 @@ class remoting {
}
$sql = "DELETE FROM remote_session WHERE remote_session = ?";
if($app->db->query($sql, $session_id) != false) {
if($app->db->query($sql, (string)$session_id) != false) {
return true;
} else {
return false;
......@@ -522,12 +565,61 @@ class remoting {
throw new SoapFault('session_id_empty', 'The SessionID is empty.');
return false;
}
if(!is_string($session_id)) {
throw new SoapFault('session_id_nostring', 'Wrong SessionID datatype.');
return false;
}
$ip_md5 = md5($_SERVER['REMOTE_ADDR']);
$sql = "SELECT * FROM `attempts_login` WHERE `ip`= ? AND `login_time` > (NOW() - INTERVAL 5 MINUTE) LIMIT 1";
$alreadyfailed = $app->db->queryOneRecord($sql, $ip_md5);
if($alreadyfailed['times'] >= 10) {
throw new SoapFault('session_failure_limit', 'The Session failure limit has been reached.');
return false;
}
$sql = "SELECT * FROM remote_session WHERE remote_session = ? AND tstamp >= UNIX_TIMESTAMP()";
$session = $app->db->queryOneRecord($sql, $session_id);
$session = $app->db->queryOneRecord($sql, (string)$session_id);
if(!is_array($session)) {
if(!$alreadyfailed['times'] )
{
//* user login the first time wrong
$sql = "INSERT INTO `attempts_login` (`ip`, `times`, `login_time`) VALUES (?, 1, NOW())";
$app->db->query($sql, $ip_md5);
} elseif($alreadyfailed['times'] >= 1) {
//* update times wrong
$sql = "UPDATE `attempts_login` SET `times`=`times`+1, `login_time`=NOW() WHERE `ip` = ? ORDER BY `login_time` DESC LIMIT 1";
$app->db->query($sql, $ip_md5);
}
throw new SoapFault('session_does_not_exist', 'The Session is expired or does not exist.');
return false;
}
$ip = $_SERVER['REMOTE_ADDR'];
if($session['remote_ip'] != $ip) {
throw new SoapFault('session_ip_mismatch', 'Session IP mismatch.');
return false;
}
if($session['remote_userid'] > 0) {
return $session;
} else {
if(!$alreadyfailed['times'] )
{
//* user login the first time wrong
$sql = "INSERT INTO `attempts_login` (`ip`, `times`, `login_time`) VALUES (?, 1, NOW())";
$app->db->query($sql, $ip_md5);
} elseif($alreadyfailed['times'] >= 1) {
//* update times wrong
$sql = "UPDATE `attempts_login` SET `times`=`times`+1, `login_time`=NOW() WHERE `ip` = ? AND `login_time` < NOW() ORDER BY `login_time` DESC LIMIT 1";
$app->db->query($sql, $ip_md5);
}
throw new SoapFault('session_does_not_exist', 'The Session is expired or does not exist.');
return false;
}
......
......@@ -232,7 +232,7 @@ class remoting_lib extends tform_base {
if(@is_numeric($primary_id)) {
if($primary_id > 0) {
// Return a single record
return parent::getDataRecord($primary_id);
return parent::getDataRecord(intval($primary_id));
} elseif($primary_id == -1) {
// Return a array with all records
$sql = "SELECT * FROM ??";
......@@ -255,8 +255,8 @@ class remoting_lib extends tform_base {
} else {
$sql_where .= "?? = ? AND ";
}
$params[] = $key;
$params[] = $val;
$params[] = (string)$key;
$params[] = (string)$val;
}
$sql_where = substr($sql_where, 0, -5);
if($sql_where == '') $sql_where = '1';
......
......@@ -70,9 +70,9 @@ class session {
function read ($session_id) {
if($this->timeout > 0) {
$rec = $this->db->queryOneRecord("SELECT * FROM sys_session WHERE session_id = ? AND (`permanent` = 'y' OR last_updated >= DATE_SUB(NOW(), INTERVAL ? MINUTE))", $session_id, $this->timeout);
$rec = $this->db->queryOneRecord("SELECT * FROM sys_session WHERE session_id = ? AND (`permanent` = 'y' OR last_updated >= DATE_SUB(NOW(), INTERVAL ? MINUTE))", (string)$session_id, $this->timeout);
} else {
$rec = $this->db->queryOneRecord("SELECT * FROM sys_session WHERE session_id = ?", $session_id);
$rec = $this->db->queryOneRecord("SELECT * FROM sys_session WHERE session_id = ?", (string)$session_id);
}
if (is_array($rec)) {
......@@ -91,18 +91,18 @@ class session {
// Dont write session_data to DB if session data has not been changed after reading it.
if(isset($this->session_array['session_data']) && $this->session_array['session_data'] != '' && $this->session_array['session_data'] == $session_data) {
$this->db->query("UPDATE sys_session SET last_updated = NOW() WHERE session_id = ?", $session_id);
$this->db->query("UPDATE sys_session SET last_updated = NOW() WHERE session_id = ?", (string)$session_id);
return true;
}
if (@$this->session_array['session_id'] == '') {
$sql = "REPLACE INTO sys_session (session_id,date_created,last_updated,session_data,permanent) VALUES (?,NOW(),NOW(),?,?)";
$this->db->query($sql, $session_id, $session_data, ($this->permanent ? 'y' : 'n'));
$this->db->query($sql, (string)$session_id, $session_data, ($this->permanent ? 'y' : 'n'));
} else {
$sql = "UPDATE sys_session SET last_updated = NOW(), session_data = ?" . ($this->permanent ? ", `permanent` = 'y'" : "") . " WHERE session_id = ?";
$this->db->query($sql, $session_data, $session_id);
$this->db->query($sql, $session_data, (string)$session_id);
}
......@@ -112,7 +112,7 @@ class session {
function destroy ($session_id) {
$sql = "DELETE FROM sys_session WHERE session_id = ?";
$this->db->query($sql, $session_id);
$this->db->query($sql, (string)$session_id);
return true;
}
......
......@@ -96,7 +96,7 @@ if(count($_POST) > 0) {
/* this is the reseller, that shall be re-logged in */
$sql = "SELECT * FROM sys_user WHERE USERNAME = ? and PASSWORT = ?";
$tmp = $app->db->queryOneRecord($sql, $username, $password);
$tmp = $app->db->queryOneRecord($sql, (string)$username, (string)$password);
$client_group_id = $app->functions->intval($tmp['default_group']);
$tmp_client = $app->db->queryOneRecord("SELECT client.client_id FROM sys_group, client WHERE sys_group.client_id = client.client_id and sys_group.groupid = ?", $client_group_id);
......@@ -118,7 +118,7 @@ if(count($_POST) > 0) {
/* this is the user the reseller wants to 'login as' */
$sql = "SELECT * FROM sys_user WHERE USERNAME = ? and PASSWORT = ?";
$tmp = $app->db->queryOneRecord($sql, $username, $password);
$tmp = $app->db->queryOneRecord($sql, (string)$username, (string)$password);
$tmp_client = $app->db->queryOneRecord("SELECT client.client_id, client.parent_client_id FROM sys_group, client WHERE sys_group.client_id = client.client_id and sys_group.groupid = ?", $tmp["default_group"]);
if(!$tmp || $tmp_client["parent_client_id"] != $res_client["client_id"]) {
......@@ -146,13 +146,13 @@ if(count($_POST) > 0) {
if ($loginAs){
$sql = "SELECT * FROM sys_user WHERE USERNAME = ? and PASSWORT = ?";
$user = $app->db->queryOneRecord($sql, $username, $password);
$user = $app->db->queryOneRecord($sql, (string)$username, (string)$password);
} else {
if(stristr($username, '@')) {
//* mailuser login
$sql = "SELECT * FROM mail_user WHERE login = ? or email = ?";
$mailuser = $app->db->queryOneRecord($sql, $username, $app->functions->idn_encode($username));
$mailuser = $app->db->queryOneRecord($sql, (string)$username, $app->functions->idn_encode($username));
$user = false;
if($mailuser) {
$saved_password = stripslashes($mailuser['password']);
......@@ -184,7 +184,7 @@ if(count($_POST) > 0) {
} else {
//* normal cp user login
$sql = "SELECT * FROM sys_user WHERE USERNAME = ?";
$user = $app->db->queryOneRecord($sql, $username);
$user = $app->db->queryOneRecord($sql, (string)$username);
if($user) {
$saved_password = stripslashes($user['passwort']);
if(substr($saved_password, 0, 1) == '$') {
......@@ -199,7 +199,7 @@ if(count($_POST) > 0) {
} else {
// update password with secure algo
$sql = 'UPDATE `sys_user` SET `passwort` = ? WHERE `username` = ?';
$app->db->query($sql, $app->auth->crypt_password($password), $username);
$app->db->query($sql, $app->auth->crypt_password($password), (string)$username);
}
}
} else {
......
......@@ -61,7 +61,7 @@ if($app->auth->get_client_limit($userid, 'mailcatchall') != 0)
'html_id' => 'mail_domain_catchall_list');
}
if(! $app->auth->is_admin())
if(! $app->auth->is_admin() && $app->auth->get_client_limit($userid, 'mail_wblist') != 0)
{
$items[] = array( 'title' => 'Email Whitelist',
'target' => 'content',
......
......@@ -1371,6 +1371,7 @@ class system{
* Control services to restart etc
*
*/
/*
function daemon_init($daemon, $action){
//* $action = start|stop|restart|reload
global $app;
......@@ -1409,7 +1410,7 @@ class system{
}
}
}
}
} */
function netmask($netmask){
list($f1, $f2, $f3, $f4) = explode('.', trim($netmask));
......@@ -2064,36 +2065,103 @@ class system{
}
function _getinitcommand($servicename, $action, $init_script_directory = '', $check_service) {
global $conf;
global $conf, $app;
// upstart
/* removed upstart support - deprecated
if(is_executable('/sbin/initctl')){
exec('/sbin/initctl version 2>/dev/null | /bin/grep -q upstart', $retval['output'], $retval['retval']);
if(intval($retval['retval']) == 0) return 'service '.$servicename.' '.$action;
}
*/
if(!in_array($action,array('restart','reload','force-reload'))) {
$app->log('Invalid init command action '.$action,LOGLEVEL_WARN);
return false;
}
// systemd
//* systemd (now default in all supported OS)
if(is_executable('/bin/systemd') || is_executable('/usr/bin/systemctl')){
if ($check_service) {
$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';
$app->log('Trying to use Systemd to restart service',LOGLEVEL_DEBUG);
//* Test service name via regex
if(preg_match('/[a-zA-Z0-9\.\-\_]/',$servicename)) {
//* Test if systemd service is enabled
if ($check_service) {
$this->exec_safe("systemctl is-enabled ? 2>&1", $servicename);
$ret_val = $this->last_exec_retcode();
} else {
$app->log('Systemd service '.$servicename.' not found or not enabled.',LOGLEVEL_DEBUG);
}
//* Return service command
if ($ret_val == 0 || !$check_service) {
return 'systemctl '.$action.' '.$servicename.'.service';
} else {
$app->log('Failed to use Systemd to restart service '.$servicename.', we try init script instead.',LOGLEVEL_DEBUG);
}
} else {
$app->log('Systemd service name contains invalid chars: '.$servicename,LOGLEVEL_DEBUG);
}
} else {
$app->log('Not using Systemd to restart services',LOGLEVEL_DEBUG);
}
// sysvinit
//* sysvinit fallback
$app->log('Using init script to restart service',LOGLEVEL_DEBUG);
//* Get init script directory
if($init_script_directory == '') $init_script_directory = $conf['init_scripts'];
if(substr($init_script_directory, -1) === '/') $init_script_directory = substr($init_script_directory, 0, -1);
if($check_service && is_executable($init_script_directory.'/'.$servicename)) {
return $init_script_directory.'/'.$servicename.' '.$action;
$init_script_directory = realpath($init_script_directory);
//* Check init script dir
if(!is_dir($init_script_directory)) {
$app->log('Init script directory '.$init_script_directory.' not found',LOGLEVEL_WARN);
return false;
}
//* Forbidden init script paths
if(substr($init_script_directory,0,4) == '/var' || substr($init_script_directory,0,4) == '/tmp') {
$app->log('Do not put init scripts in /var or /tmp folder.',LOGLEVEL_WARN);
return false;
}
//* Check init script dir owner
if(fileowner($init_script_directory) !== 0) {
$app->log('Init script directory '.$init_script_directory.' not owned by root user',LOGLEVEL_WARN);
return false;
}
$full_init_script_path = realpath($init_script_directory.'/'.$servicename);
if($full_init_script_path == '') {
$app->log('No init script, we quit here.',LOGLEVEL_WARN);
return false;
}
//* Check init script
if(!is_file($full_init_script_path)) {
$app->log('Init script '.$full_init_script_path.' not found',LOGLEVEL_WARN);
return false;
}
//* Check init script owner
if(fileowner($full_init_script_path) !== 0) {
$app->log('Init script '.$full_init_script_path.' not owned by root user',LOGLEVEL_WARN);
return false;
}
if($check_service && is_executable($full_init_script_path)) {
return $full_init_script_path.' '.$action;
}
if (!$check_service) {
return $init_script_directory.'/'.$servicename.' '.$action;
return $full_init_script_path.' '.$action;
}
}
function getinitcommand($servicename, $action, $init_script_directory = '', $check_service=false) {
function getinitcommand($servicename, $action, $init_script_directory = '', $check_service=true) {
if (is_array($servicename)) {
foreach($servicename as $service) {
$out = $this->_getinitcommand($service, $action, $init_script_directory, true);
......
......@@ -242,15 +242,20 @@ class web_module {
return $retval;
}
}
exec($cmd.' 2>&1', $retval['output'], $retval['retval']);
$app->log("Restarting httpd: $cmd", LOGLEVEL_DEBUG);
if($cmd != '') {
exec($cmd.' 2>&1', $retval['output'], $retval['retval']);
} else {
$app->log('We got no init command, restart or reload of service aborted.',LOGLEVEL_WARN);
}
// if restart failed despite successful syntax check => try again
if($web_config['server_type'] == 'nginx' && $retval['retval'] > 0){
sleep(2);
exec($cmd.' 2>&1', $retval['output'], $retval['retval']);
}
$app->log("Restarting httpd: $cmd", LOGLEVEL_DEBUG);
// nginx: do a syntax check because on some distributions, the init script always returns 0 - even if the syntax is not ok (how stupid is that?)
//if($web_config['server_type'] == 'nginx' && $retval['retval'] == 0){
......@@ -307,10 +312,16 @@ class web_module {
}
*/
}
$retval = array('output' => '', 'retval' => 0);
exec($initcommand.' 2>&1', $retval['output'], $retval['retval']);
$app->log("Restarting php-fpm: $initcommand", LOGLEVEL_DEBUG);
if($initcommand != '') {
$retval = array('output' => '', 'retval' => 0);
exec($initcommand.' 2>&1', $retval['output'], $retval['retval']);
} else {
$app->log('We got no init command, restart or reload of php-fpm service aborted.',LOGLEVEL_WARN);
}
return $retval;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment