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.
| Package | Affected versions | Patched versions |
|---|---|---|
froxlor/froxlorPackagist | < 2.3.6 | 2.3.6 |
Affected products
1Patches
1bc5e6dbaa90evalidate def_language parameter against existing language files and avoid path-traversal
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- github.com/froxlor/froxlor/commit/bc5e6dbaa90e6f3573129da640595e8c770e1d0cnvdPatchWEB
- github.com/froxlor/froxlor/security/advisories/GHSA-w59f-67xm-rxx7nvdExploitVendor AdvisoryMitigationWEB
- github.com/advisories/GHSA-w59f-67xm-rxx7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-41228ghsaADVISORY
- github.com/froxlor/froxlor/releases/tag/2.3.6nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.