GitHub Advisoryadvisory · Packagist

GHSA-xpr4-8vp6-c87j Froxlor has CSRF Vulnerability in AJAX Endpoint — Missing Cross-Site Request Forgery Protection

MEDIUM Published 2026-08-18 Modified 2026-08-18 Official page ↗
Aliases
CVE-2026-55593
Ecosystem
Packagist
Packages
froxlor/froxlor
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
Weaknesses
CWE-352
Reviewed
2026-08-18

Affected packages

• Packagist froxlor/froxlor: < 2.3.8 → fixed in 2.3.8

Description

Summary The Froxlor AJAX endpoint (`lib/ajax.php`) is missing Cross-Site Request Forgery (CSRF) protection. While the main application (`lib/init.php`) enforces CSRF token validation on all state-changing HTTP requests (POST/PUT/PATCH/DELETE), the standalone `lib/ajax.php` endpoint bypasses this mechanism entirely, validating only the user's session. An attacker can craft a malicious webpage that, when visited by an authenticated Froxlor administrator, silently modifies API key properties (e.g., adding the attacker's IP to the `allowed_from` whitelist or extending the `valid_until` expiration). --- Affected Component • File: `lib/ajax.php` — the AJAX endpoint entry point (bypasses `lib/init.php`) • File: `lib/Froxlor/Ajax/Ajax.php:66-92` — `Ajax::handle()` (no CSRF check before routing) • File: `lib/Froxlor/Ajax/Ajax.php:257-315` — `Ajax::editApiKey()` (writes to database without CSRF check) • Version: Froxlor 2.3.7 (likely all prior 2.x versions) --- Complete Call Chain: Entry Point → Vulnerable Code Step 1: Entry Point — `lib/ajax.php` (standalone bootstrap, bypasses `lib/init.php`) ```php // lib/ajax.php:26-47 namespace Froxlor; use Froxlor\Ajax\Ajax; require_once dirname(__DIR__) . '/vendor/autoload.php'; require_once dirname(__DIR__) . '/lib/userdata.inc.php'; require_once dirname(__DIR__) . '/lib/functions.php'; require_once dirname(__DIR__) . '/lib/tables.inc.php'; // CRITICAL: This file does NOT include lib/init.php // Therefore: NO CSRF token is checked before processing the request echo (new Ajax)->handle(); ``` Contrast with normal flow: All admin/customer pages (e.g., `admin_customers.php`, `customer_domains.php`) do: ```php const AREA = 'admin'; require __DIR__ . '/lib/init.php'; // action = Request::any('action'); // theme = Request::any('theme', 'Froxlor'); UI::sendHeaders(); // Starts session, sets security headers UI::sendSslHeaders(); // HSTS headers // MISSING: CSRF token validation on POST/PUT/PATCH/DELETE } ``` Step 3: Ajax::handle() — Session Validation Only, Routes to Action ```php // lib/Froxlor/Ajax/Ajax.php:66-92 public function handle() { $this->userinfo = $this->getValidatedSession(); // Only checks: isset($_SESSION['userinfo']) // MISSING: CSRF token validation before routing // Comparison: init.php lines 363-369 WOULD check here: // if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) { // $current_token = Request::post('csrf_token', ...); // if ($current_token != CurrentUser::getField('csrf_token')) { ERROR; } // } switch ($this->action) { case 'editapikey': return $this->editApiKey(); // updateTablelisting(); // $keyid, 'af' => $allowed_from, // Attacker's IP written here 'vu' => $valid_until_db, // -1 = never expires 'aid' => $this->userinfo['adminid'], 'cid' => $cid ]); return $this->jsonResponse(['allowed_from' => $allowed_from, 'valid_until' => $valid_until]); } ``` Step 6: Evidence from Legitimate Frontend — No CSRF Token Sent Even in Normal Usage ```javascript // templates/Froxlor/assets/js/jquery/apikeys.js:9-17 // Even the legitimate frontend does NOT send a csrf_token: $.ajax({ url: "lib/ajax.php?action=editapikey", type: "POST", dataType: "json", data: { id: akid, allowed_from: _this.val(), valid_until: $('div[data-entry="' + akid + '"] #valid_until').val() // NOTE: No csrf_token field here — the backend doesn't require it }, // ... }); ``` This confirms: the backend does not validate CSRF tokens, so the frontend code does not bother sending one. --- CSRF Protection Gap: Side-by-Side Comparison | Aspect | `lib/init.php` (Normal Pages) | `lib/ajax.php` (AJAX Endpoint) | |--------|------------------------------|-------------------------------| | Includes init.php | Yes (all admin_*.php, customer_*.php) | No — standalone bootstrap | | Session validation | ✅ `CurrentUser::hasSession()` | ✅ `CurrentUser::hasSession()` | | CSRF token generation | ✅ `Froxlor::genSessionId(20)` | ❌ Not generated | | CSRF token check (POST/PUT/PATCH/DELETE) | ✅ Lines 363-369 | ❌ Missing entirely | | Rate limiting | ✅ `RateLimiter::run()` | ❌ Not called | | Area enforcement | ✅ Admin/Customer area check | ❌ Not enforced | --- Vulnerability Verification Attack Path (Complete) ``` [Attacker] Hosts malicious HTML page at https://attacker.com/csrf.html document.getElementById('csrf').submit(); │ ▼ [Victim] Froxlor administrator browses to https://attacker.com/csrf.html • Victim has an active session at https://froxlor.example.com • Session cookie: PHPSESSID=, SameSite=Lax │ ▼ [Browser] Auto-submits POST to https://froxlor.example.com/lib/ajax.php?action=editapikey • Cookie behavior depends on SameSite policy (see below) │ ▼ [Server: lib/ajax.php] → require userdata.inc.php, functions.php, tables.inc.php → (new Ajax)->handle() │ ▼ [Server: Ajax::__construct()] (Ajax.php:54-61) → $this->action = 'editapikey' (from GET query string) → UI::sendHeaders() → session_start() → NO CSRF CHECK │ ▼ [Server: Ajax::handle()] (Ajax.php:66-68) → getValidatedSession() → CurrentUser::hasSession() → TRUE (session cookie was sent with request) → NO CSRF CHECK before routing │ ▼ [Server: Ajax::editApiKey()] (Ajax.php:257-315) → $keyid = 1 (from POST) → $allowed_from = 'ATTACKER_IP' (from POST) → $valid_until_db = -1 (from POST, parsed) → UPDATE api_keys SET allowed_from='ATTACKER_IP', valid_until=-1 WHERE id=1 │ ▼ [Impact] API key #1 now allows connections from ATTACKER_IP, never expires ``` SameSite=Lax Analysis Froxlor sets session cookie with `SameSite=Lax` …

Related

Alias · CVE (1)

  • CVE-2026-55593 Froxlor is open source server administration software. vulnerability

Weakness · CWE (1)

  • CWE-352 Cross-Site Request Forgery weakness

References