Remote Command Execution by Improper Escaping of Output in froxlor/froxlor
Reported on
Jun 22nd 2023
Description
Improper Encoding or Escaping of Output in Froxlor export configuration. Hackers can use it to create a json file with PHP code inside then trigger the code by set php-fpm to process .json extension.
foreach ($_POST['system'] as $sysdaemon) {
$params['system'][] = $sysdaemon;
}
$params_content = json_encode($params);
Steps To Reproduce
First, login to Froxlor by using admin account.
Access /admin_configfiles.php?page=configfiles to export config file
Use burpsuite to intercept request
we get json file location
and json file content
All parameters http, dns, smtp, mail and http have been escaping properly, but the system parameters are not. I can still inject php tag <> .
So i will POST this
http=apache24&dns=bind&smtp=x&mail=x&ftp=x&system%+<%3f%3dsystem(`/tmp/x`)%3f>%5D=aaa&csrf_token=e78bc097a3c11650887325b467abafc4aadfd59a&finish=1
which is
http=apache24&dns=bind&smtp=x&mail=x&ftp=x&system% <?=system(`/tmp/x`)?>]=aaa&csrf_token=e78bc097a3c11650887325b467abafc4aadfd59a&finish=1
For creating sh file in /tmp, i create a customer and customer's doamin. Next, I use customer ftp account to upload webshell and create sh file in /tmp. /tmp/x's content:
#!/bin/bash
id > /tmp/id.txt
After exporting the config, I get json file location
check the json file which contains php code
If hackers set allow extension .json in the setting, when visit the json on browser, php code will execute.
after access json file on browser
Impact
Hackers can run OS commands with froxlorlocal permission so they can interact with web server such as: deface, dump data,...etc
Could you test whether the following patch resolves this issue?
diff --git a/admin_configfiles.php b/admin_configfiles.php
index ac4630db..00ff67b0 100644
--- a/admin_configfiles.php
+++ b/admin_configfiles.php
@@ -99,6 +99,16 @@ if ($userinfo['change_serversettings'] == '1') {
foreach ($_POST['system'] as $sysdaemon) {
$params['system'][] = $sysdaemon;
}
+ // validate params
+ foreach ($params as $key => $value) {
+ if (!is_array($value)) {
+ $params[$key] = \Froxlor\Validate\Validate::validate($value, $key);
+ } else {
+ foreach ($value as $subkey => $subvalue) {
+ $params[$key][$subkey] = \Froxlor\Validate\Validate::validate($subvalue, $key.'.'.$subkey);
+ }
+ }
+ }
$params_content = json_encode($params);
$params_filename = FileDir::makeCorrectFile(Froxlor::getInstallDir() . 'install/' . Froxlor::genSessionId() . '.json');
file_put_contents($params_filename, $params_content);
Hi! The problem is in $key variable, not in $value so you need to validate $key not $value. I suggest that you should remove $params[$key] if the code check $key contains dangerous characters.
You can add this example code between lines 101 and 102 and that will fix this bug.
foreach ($params as $key => $value) {
if (preg_match("/[\<\>\?\{\}\'\"\`]/i", $key)) {
unset($params[$key]);
}
}
as the keys of that json array are fix, how about this:
diff --git a/admin_configfiles.php b/admin_configfiles.php
index ac4630db..0770c8a7 100644
--- a/admin_configfiles.php
+++ b/admin_configfiles.php
@@ -33,6 +33,7 @@ use Froxlor\Settings;
use Froxlor\UI\Panel\UI;
use Froxlor\UI\Request;
use Froxlor\UI\Response;
+use Froxlor\Validate\Validate;
if ($userinfo['change_serversettings'] == '1') {
if ($action == 'setconfigured') {
@@ -91,6 +92,7 @@ if ($userinfo['change_serversettings'] == '1') {
}
if ($distribution != "" && isset($_POST['finish'])) {
+ $valid_keys = ['http', 'dns', 'smtp', 'mail', 'ftp', 'system', 'distro'];
unset($_POST['finish']);
unset($_POST['csrf_token']);
$params = $_POST;
@@ -99,6 +101,20 @@ if ($userinfo['change_serversettings'] == '1') {
foreach ($_POST['system'] as $sysdaemon) {
$params['system'][] = $sysdaemon;
}
+ // validate params
+ foreach ($params as $key => $value) {
+ if (!in_array($key, $valid_keys)) {
+ unset($params[$key]);
+ continue;
+ }
+ if (!is_array($value)) {
+ $params[$key] = Validate::validate($value, $key);
+ } else {
+ foreach ($value as $subkey => $subvalue) {
+ $params[$key][$subkey] = Validate::validate($subvalue, $key.'.'.$subkey);
+ }
+ }
+ }
$params_content = json_encode($params);
$params_filename = FileDir::makeCorrectFile(Froxlor::getInstallDir() . 'install/' . Froxlor::genSessionId() . '.json');
file_put_contents($params_filename, $params_content);
That's it ! This patch will resolve this issue. Good work!