Every CVE Search/ Get API key

CVE-2026-90935

Froxlor before 2.3.7 Authorization Bypass via Mysqls.add API

5.3 MediumFix available

Froxlor before 2.3.7 fails to validate the mysql_server parameter against a customer's allowed_mysqlserver allowlist in the Mysqls.add API command. Attackers can supply a disallowed server index to create MySQL databases and users on forbidden servers, bypassing per-customer access controls.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
Low
Availability
None

Exploit likelihood

0.29%Up 0.1 points since Sep 15, 2026
90%50%10%1%

What each source reports

CVE List Score used 5.3

Froxlor before 2.3.7 Authorization Bypass via Mysqls.add API

Froxlor before 2.3.7 fails to validate the mysql_server parameter against a customer's allowed_mysqlserver allowlist in the Mysqls.add API command. Attackers can supply a disallowed server index to create MySQL databases and users on forbidden servers, bypassing per-customer access controls.

Record
CVE-2026-90935
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Weakness
CWE-285
Updated
2026-11-02
EUVDEUVD-2026-77453 5.3

Froxlor before 2.3.7 fails to validate the mysql_server parameter against a customer's allowed_mysqlserver allowlist in the Mysqls.add API command. Attackers can supply a disallowed server index to create MySQL databases and users on forbidden servers, bypassing per-customer access controls.

Record
EUVD-2026-77453
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-q4rm-m6xh-5pv7 4.3

Froxlor customer can create MySQL databases on disallowed servers via Mysqls.add API

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()):

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:
    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…
Record
GHSA-q4rm-m6xh-5pv7
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Weakness
CWE-285
Updated
2026-11-02
GitHub AdvisoriesGHSA-w63q-47vh-4f98 5.3

Froxlor before 2.3.7 fails to validate the mysql_server parameter against a customer's...

Froxlor before 2.3.7 fails to validate the mysql_server parameter against a customer's allowed_mysqlserver allowlist in the Mysqls.add API command. Attackers can supply a disallowed server index to create MySQL databases and users on forbidden servers, bypassing per-customer access controls.

Record
GHSA-w63q-47vh-4f98
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Weakness
CWE-285
Updated
2026-11-02
NVD 5.3

Froxlor before 2.3.7 fails to validate the mysql_server parameter against a customer's allowed_mysqlserver allowlist in the Mysqls.add API command. Attackers can supply a disallowed server index to create MySQL databases and users on forbidden servers, bypassing per-customer access controls.

Record
CVE-2026-90935
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Weakness
CWE-285
Updated
2026-11-02
OSV 5.3

Froxlor before 2.3.7 Authorization Bypass via Mysqls.add API

Froxlor before 2.3.7 fails to validate the mysql_server parameter against a customer's allowed_mysqlserver allowlist in the Mysqls.add API command. Attackers can supply a disallowed server index to create MySQL databases and users on forbidden servers, bypassing per-customer access controls.

Record
CVE-2026-90935
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-285
Updated
2026-11-02
OSVGHSA-q4rm-m6xh-5pv7 4.3

Froxlor customer can create MySQL databases on disallowed servers via Mysqls.add API

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()):

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:
    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…
Record
GHSA-q4rm-m6xh-5pv7
CVSS 3.1
4.3 MediumCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Weakness
CWE-285
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
froxlor/froxlor
Packagist
through 2.3.6
2.3.7
froxlor/froxlor
Packagist
before 2.3.7, plus listed versions
0.10.00.10.0-rc10.10.0-rc20.10.10.10.10
Show 45 moreShow less
0.10.110.10.120.10.130.10.140.10.150.10.160.10.170.10.180.10.190.10.20.10.200.10.210.10.220.10.230.10.23.10.10.240.10.250.10.260.10.270.10.280.10.290.10.29.10.10.30.10.300.10.310.10.320.10.330.10.340.10.34.10.10.350.10.35.10.10.360.10.370.10.380.10.38.10.10.38.20.10.38.30.10.40.10.50.10.60.10.70.10.80.10.92.0.02.0.1
2.3.7

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-90935 is a medium vulnerability (CVSS 5.3) in Froxlor, published July 2, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.29%, the 22nd percentile. Fixed in froxlor/froxlor 2.3.7.

References

This page doesn't list everything

Left out here: 58 versions and the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open