VYPR
Critical severity9.9NVD Advisory· Published Apr 23, 2026· Updated Apr 27, 2026

CVE-2026-41228

CVE-2026-41228

Description

Froxlor is open source server administration software. Prior to version 2.3.6, the Froxlor API endpoint Customers.update (and Admins.update) does not validate the def_language parameter against the list of available language files. An authenticated customer can set def_language to a path traversal payload (e.g., ../../../../../var/customers/webs/customer1/evil), which is stored in the database. On subsequent requests, Language::loadLanguage() constructs a file path using this value and executes it via require, achieving arbitrary PHP code execution as the web server user. Version 2.3.6 fixes the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
froxlor/froxlorPackagist
< 2.3.62.3.6

Affected products

1

Patches

1
bc5e6dbaa90e

validate def_language parameter against existing language files and avoid path-traversal

https://github.com/froxlor/froxlorMichael KaufmannMar 29, 2026via ghsa
3 files changed · +19 0
  • lib/Froxlor/Api/Commands/Admins.php+7 0 modified
    @@ -31,6 +31,7 @@
     use Froxlor\Database\Database;
     use Froxlor\FroxlorLogger;
     use Froxlor\Idna\IdnaWrapper;
    +use Froxlor\Language;
     use Froxlor\Settings;
     use Froxlor\System\Crypt;
     use Froxlor\UI\Response;
    @@ -254,6 +255,9 @@ public function add()
     			$idna_convert = new IdnaWrapper();
     			$email = $idna_convert->encode(Validate::validate($email, 'email', '', '', [], true));
     			$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
    +			if (!empty($def_language) && !isset(Language::getLanguages()[$def_language])) {
    +				$def_language = Settings::Get('panel.standardlanguage');
    +			}
     			$custom_notes = Validate::validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', Validate::REGEX_CONF_TEXT, '', [], true);
     
     			if (Settings::Get('system.mail_quota_enabled') != '1') {
    @@ -598,6 +602,9 @@ public function update()
     				$idna_convert = new IdnaWrapper();
     				$email = $idna_convert->encode(Validate::validate($email, 'email', '', '', [], true));
     				$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
    +				if (!empty($def_language) && !isset(Language::getLanguages()[$def_language])) {
    +					$def_language = Settings::Get('panel.standardlanguage');
    +				}
     				$custom_notes = Validate::validate(str_replace("\r\n", "\n", $custom_notes ?? ""), 'custom_notes', Validate::REGEX_CONF_TEXT, '', [], true);
     				$theme = Validate::validate($theme, 'theme', '', '', [], true);
     				$password = Validate::validate($password, 'password', '', '', [], true);
    
  • lib/Froxlor/Api/Commands/Customers.php+8 0 modified
    @@ -34,6 +34,7 @@
     use Froxlor\FileDir;
     use Froxlor\FroxlorLogger;
     use Froxlor\Idna\IdnaWrapper;
    +use Froxlor\Language;
     use Froxlor\Settings;
     use Froxlor\System\Cronjob;
     use Froxlor\System\Crypt;
    @@ -382,6 +383,9 @@ public function add()
     				$email = $idna_convert->encode(Validate::validate($email, 'email', '', '', [], true));
     				$customernumber = Validate::validate($customernumber, 'customer number', '/^[A-Za-z0-9 \-]*$/Di', '', [], true);
     				$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
    +				if (!empty($def_language) && !isset(Language::getLanguages()[$def_language])) {
    +					$def_language = Settings::Get('panel.standardlanguage');
    +				}
     				$custom_notes = Validate::validate(str_replace("\r\n", "\n", $custom_notes), 'custom_notes', Validate::REGEX_CONF_TEXT, '', [], true);
     
     				if (Settings::Get('system.mail_quota_enabled') != '1') {
    @@ -1205,6 +1209,10 @@ public function update()
     
     		}
     		$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
    +		if (!empty($def_language) && !isset(Language::getLanguages()[$def_language])) {
    +			$def_language = Settings::Get('panel.standardlanguage');
    +		}
    +
     		$theme = Validate::validate($theme, 'theme', '', '', [], true);
     
     		if (Settings::Get('system.mail_quota_enabled') != '1') {
    
  • lib/Froxlor/Language.php+4 0 modified
    @@ -88,6 +88,10 @@ public static function getTranslation(string $identifier, array $arguments = [])
     	 */
     	private static function loadLanguage($iso): array
     	{
    +		// Reject path traversal attempts
    +		if ($iso !== basename($iso) || str_contains($iso, '..')) {
    +			return [];
    +		}
     		$languageFile = dirname(__DIR__, 2) . sprintf('/lng/%s.lng.php', $iso);
     
     		if (!file_exists($languageFile)) {
    

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.