Compare commits

...

8 Commits
2.1.7 ... 2.1.9

Author SHA1 Message Date
Michael Kaufmann
1347b877a5 set version to 2.1.9 for security bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-03 07:58:15 +02:00
Michael Kaufmann
a862307bce Merge pull request from GHSA-x525-54hf-xr53
* do not log unvalidated user-input to mysql-log (if enabled)

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

* clean log-text to only allow a subset of special characters

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

* clean log-text when selecting from database to avoid possible previously added malicious entries

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>

---------

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-05-03 07:56:40 +02:00
Michael Kaufmann
2f03eee9aa add compatibility for mariadb-dump executable instead of mysqldump
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-04-27 10:24:52 +02:00
Michael Kaufmann
f4183b020b set version to 2.1.8 for bugfix release
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-29 11:27:32 +01:00
Michael Kaufmann
9a3d88e8c9 fix domains speciallogfile ajax-check/note; improve ajax ip check in admin_ipsandports
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-27 11:08:45 +01:00
Michael Kaufmann
c9460fd58f also add logfiles to virtual-host if it's a redirect
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-27 10:17:48 +01:00
Michael Kaufmann
6ef532b470 fix missing csrf tokens for some ajax requests
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2024-03-27 10:17:37 +01:00
Wiebe Cazemier
5909401cdd Fix "expires" option cannot have a year greater than 9999 (#1246)
This fixes the exception: '"expires" option cannot have a year greater
than 9999', which happens on upgrade from Debian 11 to 12. The session
timeout in the DB is 9999999999999, so we constrain the value.
2024-03-25 08:22:00 +01:00
17 changed files with 85 additions and 34 deletions

View File

@@ -35,6 +35,7 @@ return [
'varname' => 'sessiontimeout', 'varname' => 'sessiontimeout',
'type' => 'number', 'type' => 'number',
'min' => 60, 'min' => 60,
'max' => 31536000,
'default' => 600, 'default' => 600,
'save_method' => 'storeSettingField' 'save_method' => 'storeSettingField'
], ],

View File

@@ -142,8 +142,10 @@ if (($page == 'ipsandports' || $page == 'overview') && $userinfo['change_servers
} }
} elseif ($action == 'jqCheckIP') { } elseif ($action == 'jqCheckIP') {
$ip = $_POST['ip'] ?? ""; $ip = $_POST['ip'] ?? "";
if ((filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE) == false) { if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {
// returns notice if private network detected so we can display it echo json_encode('<div id="ipnote" class="invalid-feedback">'.lng('error.invalidip', [$ip]).'</div>');
} elseif (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE)) {
// returns notice if private network detected, so we can display it
echo json_encode(lng('admin.ipsandports.ipnote')); echo json_encode(lng('admin.ipsandports.ipnote'));
} else { } else {
echo 0; echo 0;

View File

@@ -248,7 +248,7 @@ if ($action == '2fa_entercode') {
$rstlog = FroxlorLogger::getInstanceOf([ $rstlog = FroxlorLogger::getInstanceOf([
'loginname' => $_SERVER['REMOTE_ADDR'] 'loginname' => $_SERVER['REMOTE_ADDR']
]); ]);
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "Unknown user '" . $loginname . "' tried to login."); $rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "Unknown user tried to login.");
Response::redirectTo('index.php', [ Response::redirectTo('index.php', [
'showmessage' => '2' 'showmessage' => '2'
@@ -305,7 +305,7 @@ if ($action == '2fa_entercode') {
$rstlog = FroxlorLogger::getInstanceOf([ $rstlog = FroxlorLogger::getInstanceOf([
'loginname' => $_SERVER['REMOTE_ADDR'] 'loginname' => $_SERVER['REMOTE_ADDR']
]); ]);
$rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User '" . $loginname . "' tried to login with wrong password."); $rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User tried to login with wrong password.");
unset($userinfo); unset($userinfo);
Response::redirectTo('index.php', [ Response::redirectTo('index.php', [
@@ -624,7 +624,7 @@ if ($action == 'forgotpwd') {
$rstlog = FroxlorLogger::getInstanceOf([ $rstlog = FroxlorLogger::getInstanceOf([
'loginname' => 'password_reset' 'loginname' => 'password_reset'
]); ]);
$rstlog->logAction(FroxlorLogger::USR_ACTION, LOG_WARNING, "User '" . $loginname . "' requested to set a new password, but was not found in database!"); $rstlog->logAction(FroxlorLogger::USR_ACTION, LOG_WARNING, "Unknown user requested to set a new password, but was not found in database!");
$message = lng('login.usernotfound'); $message = lng('login.usernotfound');
} }

View File

@@ -726,7 +726,7 @@ opcache.validate_timestamps'),
('panel', 'logo_overridecustom', '0'), ('panel', 'logo_overridecustom', '0'),
('panel', 'settings_mode', '0'), ('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'), ('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.1.7'), ('panel', 'version', '2.1.9'),
('panel', 'db_version', '202312120'); ('panel', 'db_version', '202312120');

View File

@@ -294,3 +294,13 @@ if (Froxlor::isFroxlorVersion('2.1.6')) {
Update::showUpdateStep("Updating from 2.1.6 to 2.1.7", false); Update::showUpdateStep("Updating from 2.1.6 to 2.1.7", false);
Froxlor::updateToVersion('2.1.7'); Froxlor::updateToVersion('2.1.7');
} }
if (Froxlor::isFroxlorVersion('2.1.7')) {
Update::showUpdateStep("Updating from 2.1.7 to 2.1.8", false);
Froxlor::updateToVersion('2.1.8');
}
if (Froxlor::isFroxlorVersion('2.1.8')) {
Update::showUpdateStep("Updating from 2.1.8 to 2.1.9", false);
Froxlor::updateToVersion('2.1.9');
}

View File

@@ -90,6 +90,8 @@ class SysLog extends ApiCommand implements ResourceEntity
} }
Database::pexecute($result_stmt, $query_fields, true, true); Database::pexecute($result_stmt, $query_fields, true, true);
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) { while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
// clean log-text
$row['text'] = preg_replace("/[^\w @#\"':.()\[\]+\-_\/\\\!]/i", "_", $row['text']);
$result[] = $row; $result[] = $row;
} }
$this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list log-entries"); $this->logger()->logAction($this->isAdmin() ? FroxlorLogger::ADM_ACTION : FroxlorLogger::USR_ACTION, LOG_INFO, "[API] list log-entries");

View File

@@ -823,6 +823,7 @@ class Apache extends HttpConfigBase
$modrew_red = ' [R=' . $code . ';L,NE]'; $modrew_red = ' [R=' . $code . ';L,NE]';
} }
$vhost_content .= $this->getLogfiles($domain);
// redirect everything, not only root-directory, #541 // redirect everything, not only root-directory, #541
$vhost_content .= ' <IfModule mod_rewrite.c>' . "\n"; $vhost_content .= ' <IfModule mod_rewrite.c>' . "\n";
$vhost_content .= ' RewriteEngine On' . "\n"; $vhost_content .= ' RewriteEngine On' . "\n";

View File

@@ -406,6 +406,7 @@ class Lighttpd extends HttpConfigBase
// Get domain's redirect code // Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']); $code = Domain::getDomainRedirectCode($domain['id']);
$vhost_content .= $this->getLogFiles($domain);
$vhost_content .= ' url.redirect-code = ' . $code . "\n"; $vhost_content .= ' url.redirect-code = ' . $code . "\n";
$vhost_content .= ' url.redirect = (' . "\n"; $vhost_content .= ' url.redirect = (' . "\n";
$vhost_content .= ' "^/(.*)$" => "' . $uri . '$1"' . "\n"; $vhost_content .= ' "^/(.*)$" => "' . $uri . '$1"' . "\n";

View File

@@ -586,6 +586,7 @@ class Nginx extends HttpConfigBase
// Get domain's redirect code // Get domain's redirect code
$code = Domain::getDomainRedirectCode($domain['id']); $code = Domain::getDomainRedirectCode($domain['id']);
$vhost_content .= $this->getLogFiles($domain);
$vhost_content .= "\t" . 'location / {' . "\n"; $vhost_content .= "\t" . 'location / {' . "\n";
$vhost_content .= "\t\t" . 'return ' . $code . ' ' . $uri . '$request_uri;' . "\n"; $vhost_content .= "\t\t" . 'return ' . $code . ' ' . $uri . '$request_uri;' . "\n";
$vhost_content .= "\t" . '}' . "\n"; $vhost_content .= "\t" . '}' . "\n";

View File

@@ -115,30 +115,46 @@ class ExportCron extends FroxlorCron
$has_dbs = false; $has_dbs = false;
$current_dbserver = -1; $current_dbserver = -1;
while ($row = $sel_stmt->fetch()) {
// Get sql_root data for the specific database-server the database resides on // look for mysqldump
if ($current_dbserver != $row['dbserver']) { $section = 'mysqldump';
Database::needRoot(true, $row['dbserver']); if (file_exists("/usr/bin/mysqldump")) {
Database::needSqlData(); $mysql_dump = '/usr/bin/mysqldump';
$sql_root = Database::getSqlData(); } elseif (file_exists("/usr/local/bin/mysqldump")) {
Database::needRoot(false); $mysql_dump = '/usr/local/bin/mysqldump';
// create temporary mysql-defaults file for the connection-credentials/details } elseif (file_exists("/usr/bin/mariadb-dump")) {
$mysqlcnf_file = tempnam("/tmp", "frx"); $mysql_dump = '/usr/bin/mariadb-dump';
$mysqlcnf = "[mysqldump]\npassword=" . $sql_root['passwd'] . "\nhost=" . $sql_root['host'] . "\n"; $section = 'mariadb-dump';
if (!empty($sql_root['port'])) { }
$mysqlcnf .= "port=" . $sql_root['port'] . "\n"; if (!isset($mysql_dump)) {
} elseif (!empty($sql_root['socket'])) { $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, 'mysqldump/mariadb-dump executable could not be found. Please install mysql-client/mariadb-client package.');
$mysqlcnf .= "socket=" . $sql_root['socket'] . "\n"; } else {
while ($row = $sel_stmt->fetch()) {
// Get sql_root data for the specific database-server the database resides on
if ($current_dbserver != $row['dbserver']) {
Database::needRoot(true, $row['dbserver']);
Database::needSqlData();
$sql_root = Database::getSqlData();
Database::needRoot(false);
// create temporary mysql-defaults file for the connection-credentials/details
$mysqlcnf_file = tempnam("/tmp", "frx");
$mysqlcnf = "[".$section."]\npassword=" . $sql_root['passwd'] . "\nhost=" . $sql_root['host'] . "\n";
if (!empty($sql_root['port'])) {
$mysqlcnf .= "port=" . $sql_root['port'] . "\n";
} elseif (!empty($sql_root['socket'])) {
$mysqlcnf .= "socket=" . $sql_root['socket'] . "\n";
}
file_put_contents($mysqlcnf_file, $mysqlcnf);
} }
file_put_contents($mysqlcnf_file, $mysqlcnf); $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> '.basename($mysql_dump) . ' -u ' . escapeshellarg($sql_root['user']) . ' -pXXXXX ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/mysql/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'));
$bool_false = false;
FileDir::safe_exec($mysql_dump . ' --defaults-file=' . escapeshellarg($mysqlcnf_file) . ' -u ' . escapeshellarg($sql_root['user']) . ' ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/mysql/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'), $bool_false, [
'>'
]);
$has_dbs = true;
$current_dbserver = $row['dbserver'];
} }
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'shell> mysqldump -u ' . escapeshellarg($sql_root['user']) . ' -pXXXXX ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/mysql/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'));
$bool_false = false;
FileDir::safe_exec('mysqldump --defaults-file=' . escapeshellarg($mysqlcnf_file) . ' -u ' . escapeshellarg($sql_root['user']) . ' ' . $row['databasename'] . ' > ' . FileDir::makeCorrectFile($tmpdir . '/mysql/' . $row['databasename'] . '_' . date('YmdHi', time()) . '.sql'), $bool_false, [
'>'
]);
$has_dbs = true;
$current_dbserver = $row['dbserver'];
} }
if ($has_dbs) { if ($has_dbs) {

View File

@@ -31,7 +31,7 @@ final class Froxlor
{ {
// Main version variable // Main version variable
const VERSION = '2.1.7'; const VERSION = '2.1.9';
// Database version (YYYYMMDDC where C is a daily counter) // Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202312120'; const DBVERSION = '202312120';

View File

@@ -175,6 +175,9 @@ class FroxlorLogger
$this->initMonolog(); $this->initMonolog();
} }
// clean log-text
$text = preg_replace("/[^\w @#\"':.()\[\]+\-_\/\\\!]/i", "_", $text);
if (self::$crondebug_flag || ($action == FroxlorLogger::CRON_ACTION && $type <= LOG_WARNING)) { if (self::$crondebug_flag || ($action == FroxlorLogger::CRON_ACTION && $type <= LOG_WARNING)) {
echo "[" . $this->getLogLevelDesc($type) . "] " . $text . PHP_EOL; echo "[" . $this->getLogLevelDesc($type) . "] " . $text . PHP_EOL;
} }

View File

@@ -176,15 +176,19 @@ class Core
$filename = "/tmp/froxlor_backup_" . date('YmdHi') . ".sql"; $filename = "/tmp/froxlor_backup_" . date('YmdHi') . ".sql";
// look for mysqldump // look for mysqldump
$section = 'mysqldump';
if (file_exists("/usr/bin/mysqldump")) { if (file_exists("/usr/bin/mysqldump")) {
$mysql_dump = '/usr/bin/mysqldump'; $mysql_dump = '/usr/bin/mysqldump';
} elseif (file_exists("/usr/local/bin/mysqldump")) { } elseif (file_exists("/usr/local/bin/mysqldump")) {
$mysql_dump = '/usr/local/bin/mysqldump'; $mysql_dump = '/usr/local/bin/mysqldump';
} elseif (file_exists("/usr/bin/mariadb-dump")) {
$mysql_dump = '/usr/bin/mariadb-dump';
$section = 'mariadb-dump';
} }
// create temporary .cnf file // create temporary .cnf file
$cnffilename = "/tmp/froxlor_dump.cnf"; $cnffilename = "/tmp/froxlor_dump.cnf";
$dumpcnf = "[mysqldump]" . PHP_EOL . "password=\"" . $this->validatedData['mysql_root_pass'] . "\"" . PHP_EOL; $dumpcnf = "[".$section."]" . PHP_EOL . "password=\"" . $this->validatedData['mysql_root_pass'] . "\"" . PHP_EOL;
file_put_contents($cnffilename, $dumpcnf); file_put_contents($cnffilename, $dumpcnf);
// make the backup // make the backup
@@ -195,7 +199,7 @@ class Core
@unlink($cnffilename); @unlink($cnffilename);
if (stristr(implode(" ", $output), "error")) { if (stristr(implode(" ", $output), "error")) {
throw new Exception(lng('install.errors.mysqldump_backup_failed')); throw new Exception(lng('install.errors.mysqldump_backup_failed'));
} else if (!file_exists($filename)) { } elseif (!file_exists($filename)) {
throw new Exception(lng('install.errors.sql_backup_file_missing')); throw new Exception(lng('install.errors.sql_backup_file_missing'));
} }
} else { } else {
@@ -379,7 +383,7 @@ class Core
$this->updateSetting($upd_stmt, 1, 'system', 'leenabled'); $this->updateSetting($upd_stmt, 1, 'system', 'leenabled');
$this->updateSetting($upd_stmt, 1, 'system', 'le_froxlor_enabled'); $this->updateSetting($upd_stmt, 1, 'system', 'le_froxlor_enabled');
} }
$this->updateSetting($upd_stmt, $this->validatedData['servername'], 'system', 'hostname'); $this->updateSetting($upd_stmt, strtolower($this->validatedData['servername']), 'system', 'hostname');
$this->updateSetting($upd_stmt, 'en', 'panel', 'standardlanguage'); // TODO: set language $this->updateSetting($upd_stmt, 'en', 'panel', 'standardlanguage'); // TODO: set language
$this->updateSetting($upd_stmt, $this->validatedData['mysql_access_host'], 'system', 'mysql_access_host'); $this->updateSetting($upd_stmt, $this->validatedData['mysql_access_host'], 'system', 'mysql_access_host');
$this->updateSetting($upd_stmt, $this->validatedData['webserver'], 'system', 'webserver'); $this->updateSetting($upd_stmt, $this->validatedData['webserver'], 'system', 'webserver');

View File

@@ -369,7 +369,7 @@ if (CurrentUser::hasSession()) {
} }
// update cookie lifetime // update cookie lifetime
$cookie_params = [ $cookie_params = [
'expires' => time() + Settings::Get('session.sessiontimeout'), 'expires' => time() + min(Settings::Get('session.sessiontimeout'), 31536000),
'path' => '/', 'path' => '/',
'domain' => UI::getCookieHost(), 'domain' => UI::getCookieHost(),
'secure' => UI::requestIsHttps(), 'secure' => UI::requestIsHttps(),

View File

@@ -31,7 +31,7 @@ export default function () {
planid: pid planid: pid
}, },
dataType: "json", dataType: "json",
beforeSend: function(request) { beforeSend: function (request) {
request.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content')); request.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
}, },
success: function (json) { success: function (json) {

View File

@@ -13,6 +13,9 @@ export default function () {
customerid: cid customerid: cid
}, },
dataType: "json", dataType: "json",
beforeSend: function (request) {
request.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
},
success: function (json) { success: function (json) {
if (json.length > 0) { if (json.length > 0) {
$('#phpsettingid option').each(function () { $('#phpsettingid option').each(function () {
@@ -45,6 +48,10 @@ export default function () {
id: $('input[name=id]').val(), newval: +$('#speciallogfile').is(':checked') id: $('input[name=id]').val(), newval: +$('#speciallogfile').is(':checked')
}, },
dataType: "json", dataType: "json",
async: false,
beforeSend: function (request) {
request.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
},
success: function (json) { success: function (json) {
if (json.changed) { if (json.changed) {
$('#speciallogfile').addClass('is-invalid'); $('#speciallogfile').addClass('is-invalid');

View File

@@ -15,6 +15,9 @@ export default function () {
ip: ipval ip: ipval
}, },
dataType: "json", dataType: "json",
beforeSend: function (request) {
request.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').getAttribute('content'));
},
success: function (json) { success: function (json) {
if (json != 0) { if (json != 0) {
$('#ip').addClass('is-invalid'); $('#ip').addClass('is-invalid');