Froxlor Admin-to-Root Privilege Escalation via Input Validation Bypass + OS Command Injection
Description
Froxlor is open source server administration software. Prior to 2.3.4, a typo in Froxlor's input validation code (== instead of =) completely disables email format checking for all settings fields declared as email type. This allows an authenticated admin to store arbitrary strings in the panel.adminmail setting. This value is later concatenated into a shell command executed as root by a cron job, where the pipe character | is explicitly whitelisted. The result is full root-level Remote Code Execution. This vulnerability is fixed in 2.3.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
froxlor/froxlorPackagist | < 2.3.4 | 2.3.4 |
Affected products
1Patches
122249677107ffix validation of email and url fields in settings, properly escape shell arguments in config-services and acme.sh installation
3 files changed · +11 −8
lib/Froxlor/Cli/ConfigServices.php+8 −5 modified@@ -218,7 +218,7 @@ private function createConfig(OutputInterface $output, SymfonyStyle $io): int $_daemons_config['distro'] = $io->choice('Choose distribution', $valid_dists, $os_default); // go through all services and let user check whether to include it or not - if (empty($_daemons_config['distro']) || !file_exists($config_dir . '/' . $_daemons_config['distro']. ".xml")) { + if (empty($_daemons_config['distro']) || !file_exists($config_dir . '/' . $_daemons_config['distro'] . ".xml")) { $output->writeln('<error>Empty or non-existing distribution given.</>'); return self::INVALID; } @@ -359,13 +359,16 @@ private function applyConfig(InputInterface $input, OutputInterface $output, Sym if (!empty($decoded_config)) { $config_dir = Froxlor::getInstallDir() . 'lib/configfiles/'; - if (empty($decoded_config['distro']) || !file_exists($config_dir . '/' . $decoded_config['distro']. ".xml")) { + if (empty($decoded_config['distro']) || !file_exists($config_dir . '/' . $decoded_config['distro'] . ".xml")) { $output->writeln('<error>Empty or non-existing distribution given. Please login with an admin, go to "System -> Configuration" and select your correct distribution in the top-right corner or specify valid distribution name for "distro" parameter.</>'); return self::INVALID; } - $configfiles = new ConfigParser($config_dir . '/' . $decoded_config['distro']. ".xml"); + $configfiles = new ConfigParser($config_dir . '/' . $decoded_config['distro'] . ".xml"); $services = $configfiles->getServices(); $replace_arr = $this->getReplacerArray(); + $clean_replace_arr = array_map(function ($v) { + return escapeshellarg((string)($v ?? '')); + }, $replace_arr); // be sure the fallback certificate specified in the settings exists $certFile = Settings::Get('system.ssl_cert_file'); @@ -400,13 +403,13 @@ private function applyConfig(InputInterface $input, OutputInterface $output, Sym case "install": $output->writeln("Installing required packages"); $result = null; - passthru(strtr($action['content'], $replace_arr), $result); + passthru(strtr($action['content'], $clean_replace_arr), $result); if (strlen($result) > 1) { echo $result; } break; case "command": - exec(strtr($action['content'], $replace_arr)); + exec(strtr($action['content'], $clean_replace_arr)); break; case "file": if (array_key_exists('content', $action)) {
lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php+1 −1 modified@@ -425,7 +425,7 @@ private static function checkInstall($tries = 0) if (!file_exists(self::getAcmeSh())) { FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Could not find acme.sh - installing it to /root/.acme.sh/"); $return = false; - FileDir::safe_exec("wget -O - https://get.acme.sh | sh -s email=" . Settings::Get('panel.adminmail'), $return, [ + FileDir::safe_exec("wget -O - https://get.acme.sh | sh -s email=" . escapeshellarg(Settings::Get('panel.adminmail')), $return, [ '|' ]); $set_path = self::getAcmeSh();
lib/Froxlor/Validate/Form/Data.php+2 −2 modified@@ -166,13 +166,13 @@ public static function validateFormFieldString($fieldname, $fielddata, $newfield public static function validateFormFieldEmail($fieldname, $fielddata, $newfieldvalue) { - $fielddata['string_type'] == 'mail'; + $fielddata['string_type'] = 'mail'; return self::validateFormFieldString($fieldname, $fielddata, $newfieldvalue); } public static function validateFormFieldUrl($fieldname, $fielddata, $newfieldvalue) { - $fielddata['string_type'] == 'url'; + $fielddata['string_type'] = 'url'; return self::validateFormFieldString($fieldname, $fielddata, $newfieldvalue); }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-33mp-8p67-xj7cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-26279ghsaADVISORY
- github.com/froxlor/Froxlor/security/advisories/GHSA-33mp-8p67-xj7cghsax_refsource_CONFIRMWEB
- github.com/froxlor/froxlor/commit/22249677107f8f39f8d4a238605641e87dab4343ghsax_refsource_MISCWEB
- github.com/froxlor/froxlor/releases/tag/2.3.4ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.