Threats Tagged 'ghsa-q4rm-m6xh-5pv7'
View all threats tagged with 'ghsa-q4rm-m6xh-5pv7'. Filter and sort to focus on specific types of threats.
Stop chasing alerts. Route them.
Start free, then upgrade once to turn Radar into an automated delivery engine for your security stack.
Custom feeds / Automations: email, Slack, webhooks, SIEM/MISP / API access (baseline limits)
API access activates after upgrading in Console -> Billing.
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.
Filter Threats
Narrow down the results by type, severity, or affected countries
Threats Tagged 'ghsa-q4rm-m6xh-5pv7'
Click on any threat for detailed analysis and mitigation recommendations
## Summary The `Mysqls.add` API command (`lib/Froxlor/Api/Commands/Mysqls.php`) accepts a customer-controlled `mysql_server` parameter and only validates that the value is numeric and that the server index exists in `userdata.inc.php`. It never checks the value against the calling customer's `allowed_mysqlserver` allowlist. A customer can therefore create a database, plus a MySQL user with a password they choose, on any MySQL server the operator has configured — including servers that were explicitly excluded from that customer (e.g. a separate cluster, premium-tier host, or another tenant pool). The same `allowed_mysqlserver` check is correctly enforced in `MysqlServer::get()` / `MysqlServer::listing()` and in the customer-facing UI (`customer_mysql.php`), confirming the omission is a bug, not by-design. ## Details **Vulnerable code path** — `lib/Froxlor/Api/Commands/Mysqls.php:69-99` (`add()`): ```php public function add() { if (($this->getUserDetail('mysqls_used') < $this->getUserDetail('mysqls') || ...) { ... $customer = $this->getCustomerData('mysqls'); // line 80 $dbserver = $this->getParam('mysql_server', true, // line 81 — user-controlled $this->getDefaultMySqlServer($customer)); ... $dbserver = Validate::validate($dbserver, ..., '/^[0-9]+$/', ...); // line 92 — numeric only Database::needRoot(true, $dbserver, false); // line 93 — root ctx for ANY index Database::needSqlData(); $sql_root = Database::getSqlData(); Database::needRoot(false); if (!is_array($sql_root)) { // line 97 — only existence check throw new Exception("Database server with index #" . $dbserver . " is unknown", 404); } ... $username = $dbm->createDatabase($newdb_params['loginname'], $password, $dbserver, ...); // line 116/118 — DB+user created ... Database::pexecute($stmt, ["customerid"=>$customer['customerid'], ..., "dbserver"=>$dbserver], ...); } } ``` The `$customer['allowed_mysqlserver']` field IS read on line 80 but is only consumed by `getDefaultMySqlServer()` (lines 566-573) to compute a default when the request omits `mysql_server`. As soon as the client supplies the parameter, the default path is skipped and no further authorization gate runs. **Cross-file evidence the check is intended elsewhere:** - `lib/Froxlor/Api/Commands/MysqlServer.php:319-323` — `get()` rejects with HTTP 405 when `$dbserver` is not in `allowed_mysqlserver`: ```php if ($this->isAdmin() == false) { $allowed_mysqls = json_decode($this->getUserDetail('allowed_mysqlserver'), true); if ($allowed_mysqls === false || empty($allowed_mysqls) || !in_array($dbserver, $allowed_mysqls)) { throw new Exception("You cannot access this resource", 405); } ... } ``` - `lib/Froxlor/Api/Commands/MysqlServer.php:252-257` — same allowlist filter on `listing()`. - `customer_mysql.php:222` — UI rejects with `Response::dynamicError('No permission')` when `empty($allowed_mysqlservers)`. **Chain of execution (attacker → impact):** 1. Customer authenticates to `api.php` with apikey/secret. The only API gate is `cust_api_allowed`; `allowed_mysqlserver` is not consulted at auth time. 2. Customer sends JSON `{"command":"Mysqls.add","params":{"mysql_password":"<valid>","mysql_server":<disallowed_idx>}}`. 3. `Mysqls.php:71` quota check passes (`mysqls_used < mysqls`). 4. `Mysqls.php:80` `getCustomerData('mysqls')` returns the caller's own row. 5. `Mysqls.php:81` `$dbserver` is set from the request (default-fallback path skipped). 6. `Mysqls.php:92` numeric regex passes. 7. `Mysqls.php:93-99` `Database::needRoot(true, $dbserver, false)` switches to the root context of the attacker-chosen server; existence check passes. 8. `Mysqls.php:116/118` `DbManager::createDatabase(...)` runs against the disallowed server using stored root credentials, creating the DB and granting the supplied password to `<loginname>_<sqlN>` (DbManager.php:177-218). 9. `Mysqls.php:127-141` inserts a row into `TABLE_PANEL_DATABASES` with the attacker's `customerid` and the disallowed `dbserver`, allowing later management via `Mysqls.get/update/delete` (which only filter by `customerid` for non-admins, e.g. `Mysqls.php:282`). ## PoC Preconditions on the target instance: - ≥2 MySQL servers configured in `lib/userdata.inc.php` (e.g. index 0 default, index 1 internal/premium). - Customer X with `allowed_mysqlserver=[0]`, `cust_api_allowed=1`, `mysqls > 0`, and an issued API key (`apikey:secret`). Request — customer creates a database on server `1`, which is *not* in their allowlist: ```bash curl -k -u 'CUST_APIKEY:CUST_SECRET' \ -H 'Content-Type: application/json' \ -X POST \ -d '{"command":"Mysqls.add","params":{"mysql_password":"ValidP@ssw0 Join the discussion | GCVE Database | 07/02/2026, 19:23:49 UTC Added: 07/02/2026, 22:56:57 UTC |
Showing 1 to 1 of 1 result