froxlor/froxlor: Missing authorization via unchecked `parentdomainid` in `SubDomains.delete`, enabling deletion of a reseller-assigned main domain
What fixes it
- 2.3.15froxlor/froxlor, composerGitHub
composer require "froxlor/froxlor:^2.3.15"
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
Summary
**Missing authorization (CWE-862) in SubDomains::delete: the command never checks parentdomainid, so a customer deletes a main domain its reseller assigned to it.** The guard that is supposed to stop this exists only in the presentation layer, and SubDomains::get deliberately returns main domains, so the row the customer hands to delete can be one. The delete also skips every bookkeeping step its admin-only sibling performs, which leaks the reseller's domain slot permanently and drives the customer's own subdomains_used counter negative.
Details
SubDomains::delete (lib/Froxlor/Api/Commands/SubDomains.php:1161-1211) checks caneditdomain at :1180 and never inspects parentdomainid. SubDomains::get (:493-497) has no parentdomainid restriction, so a parentdomainid = 0 main domain is returned to the customer and can be fed straight back.
The guard lives in the UI only — customer_domains.php:108-126, domains_cantdeletemaindomain.
caneditdomain is not a barrier either: for any domain added by a reseller (an admin with change_serversettings != 1) it is hard-coded to '1' in the else branch of Domains::add (Domains.php:535).
Compare the admin-only sibling Domains::delete (Domains.php:2255-2291), which deletes WHERE id = :id OR parentdomainid = :id, decrements the owning admin's domains_used, and resets panel_customers.standardsubdomain. SubDomains::delete does none of these: :1205-1211 deletes only the one row, so child subdomains are orphaned with a dangling parentdomainid; the reseller's domains_used is never decremented; and Customers::decreaseUsage(…, 'subdomains_used') at :1258 runs for a row that never counted toward that quota in the first place.
Proof of concept
froxlor 2.3.14 (c147de18), over HTTP. reseller1 (change_serversettings = 0) creates customer web2 and assigns it the main domain reseller-owned.example.test (id 4, parentdomainid = 0, caneditdomain = 1, adminid = 2). Customer web2 then deletes it with its own API key.
=== PRE ===
domain 4 reseller-owned.example.test parentdomainid=0 adminid=2
admin 2 domains=-1 domains_used=1
cust 2 subdomains=5 subdomains_used=0
$ curl -u CUST2KEY:CUST2SECRET \
-d '{"command":"SubDomains.delete","params":{"id":4}}' http://127.0.0.1/api.php
{ "data": { "id": 4, "domain": "reseller-owned.example.test", "adminid": 2, … } }
=== POST ===
domain-count 0 <- the reseller's domain is gone
admin 2 domains=-1 domains_used=1 <- NOT decremented
cust 2 subdomains=5 subdomains_used=-1 <- counter driven NEGATIVEThe oracle is the row count and the two counters read back from the database after the call, not the HTTP status.
Impact
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L — 5.4 Medium.
Customer → reseller. Destructive: the reseller loses a domain it provisioned, with the vhost, DNS and mail rows partly orphaned behind a dangling parentdomainid. And self-serve quota inflation: subdomains_used has no floor — ApiCommand.php:236-246 does a raw - 1 — so repeating the cycle grants the customer unbounded extra subdomain allowance.
Suggested fix
Reject parentdomainid = 0 rows in SubDomains::delete, the way customer_domains.php already does in the UI; and route main-domain deletion through Domains::delete, which does the cascade and the counter bookkeeping correctly.
What this is not
The deletion is still scoped to customerid = own (:1205), so a customer cannot delete another customer's domain — only one the reseller assigned to it.
The quota-denial consequence for the reseller was not demonstrated end to end. My reseller had domains = -1 (unlimited), so I proved the counter drift — domains_used not decremented, subdomains_used at -1 — and inferred, rather than showed, the allocation failure that follows on a finite quota.
Verified present at 2.3.14 and origin/v2 c147de18. Not present on origin/main (v3 rewrite).
Text from GitHub Advisory Database
Affected versions 1
- froxlor/froxlor, composer>= 2.0.0, <= 2.3.14GitHub