VYPR
Critical severityNVD Advisory· Published Mar 3, 2026· Updated Mar 4, 2026

Froxlor Admin-to-Root Privilege Escalation via Input Validation Bypass + OS Command Injection

CVE-2026-26279

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.

PackageAffected versionsPatched versions
froxlor/froxlorPackagist
< 2.3.42.3.4

Affected products

1

Patches

1
22249677107f

fix validation of email and url fields in settings, properly escape shell arguments in config-services and acme.sh installation

https://github.com/froxlor/froxlorMichael KaufmannFeb 14, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.