CVE-2026-115464: a Poweradmin user allowed only to edit records could rename the zone and slave it to their own server
Poweradmin lets an operator hand someone the records inside a DNS zone without handing over the zone itself. The web interface keeps a zone's type and master server behind a separate permission, and has no rename at all. I found that the API's zone update asked only for the records permission, so a user delegated the records of their own zone could rename it to any name not yet in the installation and turn it into a secondary of a server they control. Fixed in 4.2.6, 4.3.5 and 4.4.1.
Disclaimer. This is personal security research, carried out on my own equipment against a public open-source project. It has no connection to any current or former employer, and no client, employer or production environment was involved at any point. Every request in it went to Poweradmin running from a source checkout of the 4.4 branch under PHP 8.3’s built-in server, in a Docker container on my own machine, against MySQL 8.4 loaded with the project’s own schema. No PowerDNS daemon was involved: what I measured is the database PowerDNS reads its zones from. The issue was reported privately through the project’s security advisory process. It is written up here after the advisory, the fixed releases and the CVE identifier were public.
Poweradmin is a web front end for the PowerDNS authoritative server, and much of its point is handing out part of a DNS server to someone else. Access is built from named permissions, which the operator combines into templates. A hosting provider can give a customer the records inside the customer’s own zone and keep the zone itself: its name, whether it is a primary or a secondary, and which server a secondary copies from.
The web interface kept that promise. Poweradmin’s REST API, before 4.4.1, did not. A user trusted only with the records of their own zone could rename the zone to any name not already in the installation, and make it a secondary of a server they control, with two requests.
Records, not the zone
Two permissions divide a zone between its contents and its settings:
zone_content_edit_own: edit the records in zones you own.zone_meta_edit_own: change the zone itself, meaning its type and, for a secondary, its master server.
A template with the first and not the second is the customer account above, and the web edit page is built around the split. It computes the second permission on its own and acts on a change to the zone settings only for users who hold it:
// lib/Application/Controller/EditController.php, v4.4.0, lines 274-275 and 286
$perm_meta_edit = $this->permissionService->getZoneMetaEditPermissionLevel($userId);
$meta_edit = $perm_meta_edit == "all" || ($perm_meta_edit == "own" && $user_is_zone_owner == "1");
// ...
if ($this->isPost() && $meta_edit) {
Renaming is not offered to anyone. In 4.3 and 4.4, the repository that talks to PowerDNS through its HTTP API, instead of through the database, refuses a rename in so many words: “Zone renaming is not supported in API backend mode.”
Two requests to zone 3
The REST API takes the same user’s API key and is supposed to apply the same permissions. For the same records-only user, on their own zone, it drew the line somewhere else:
| What the user tries | Web interface | API v2, 4.4.0 |
|---|---|---|
| Edit a record | allowed | allowed |
Change the type from MASTER to SLAVE |
refused | allowed |
| Point the zone at a master server | refused | allowed |
| Rename the zone | not offered | allowed |
| Any of the above on someone else’s zone | refused | refused |
The lab account, contentop, held zone_content_edit_own and zone_content_view_own, plus search and api_manage_keys so it could use the API. It owned zone 3, tenant.example. It held neither zone_meta_edit_own nor zone_master_add, the permission to create zones. These were its requests:
PUT /api/v2/zones/3 {"name":"bigbank.example"} -> 200
PUT /api/v2/zones/3 {"type":"SLAVE","master":"203.0.113.66"} -> 200
PUT /api/v2/zones/2 {"name":"evil.example"} -> 403
The zone’s row in Poweradmin’s domains table, before and after:
3 | tenant.example | MASTER | NULL
3 | bigbank.example | SLAVE | 203.0.113.66
The 403 on zone 2, which belongs to another account, is the control: ownership was enforced. The API checked which zone the caller could touch, and never asked what it could do to that zone.
canEditZone() checks records
The handler accepts three fields, $allowedFields = ['name', 'type', 'master'], all of them zone settings, and gates the whole update on one call, canEditZone(). The name reads like the check for zone settings. The body is a records check:
// lib/Domain/Service/ApiPermissionService.php, v4.4.0, lines 196-215
public function canEditZone(int $userId, int $zoneId): bool
{
// Uberuser can edit all zones
if ($this->userHasPermission($userId, 'user_is_ueberuser')) {
return true;
}
// User with zone_content_edit_others can edit all zones
if ($this->userHasPermission($userId, 'zone_content_edit_others')) {
return true;
}
// User with zone_content_edit_own can edit their own zones
if ($this->userHasPermission($userId, 'zone_content_edit_own')) {
return $this->userOwnsZone($userId, $zoneId);
}
return false;
}
The check that does ask for zone_meta_edit_*, canEditZoneMeta(), was further down the same file. In 4.4.0 the API already used it for a zone’s metadata entries and its owners. The handler that changes the zone’s name, type and master was the one place that did not.
I took the name at face value on my first pass and nearly dropped the finding. A docblock between the two functions encourages that reading: it says a narrower records permission “must NOT grant zone-level metadata changes (name/type/master). Zone-level updates remain gated by canEditZone().” The first sentence is right. The second describes the bug.
What a secondary pulls in
I did not run a PowerDNS daemon in the lab, so what follows is how PowerDNS uses those columns, not a measurement.
A SLAVE zone has no records of its own: PowerDNS fills it by zone transfer from its master. On an installation where PowerDNS runs as a secondary, which is what Poweradmin’s slave zones are for, the next refresh replaces the zone with whatever the user’s server sends. From then on the zone’s records are written outside Poweradmin altogether, past its record validation and its permission checks.
The rename matters on a shared server. A domain can be delegated to the operator’s nameservers before anyone adds it to Poweradmin, and taking that name this way makes those nameservers answer for it with the user’s records. Choosing the name of a new zone is what zone_master_add exists to control, and contentop did not hold it.
Not on the stock templates
None of this is a gain with the four templates Poweradmin ships. The stock Zone Manager already holds zone_meta_edit_own and zone_master_add, so it can do all of it in the web interface. The stock Editor holds a narrower records permission and cannot reach the handler at all. The bug lives in templates the operator builds, records without the zone, which is exactly the separation the web edit page enforces. I also argued in the report, without running it, that a template with zone_content_edit_others, the records permission across every zone, passed the same check for every zone in the installation.
Three days
Edmondas Girkantas wrote the fix an hour and forty minutes after the report and backported it to the 4.3 and 4.2 branches the same morning. It requires canEditZoneMeta() for any change to the name, type or master. The same commit fixed the version 1 API, which had the same check in front of the same three fields and which my report had not named. A follow-up the next day lets an unchanged value through, so a client that sends the whole zone back is not refused. 4.2.6, 4.3.5 and 4.4.1 were released on 4 September, three days after the report, and in 4.5.0 canEditZone() is called hasZoneContentEditPermission().
Checking an installation
The accounts that could do this are the ones on templates granting zone_content_edit_own or zone_content_edit_others without zone_meta_edit_*, with an API key. If you have such templates and ran a version before the fix, a zone that changed name or became a secondary of an unfamiliar master shows up in the domains table:
SELECT id, name, type, master FROM domains WHERE type = 'SLAVE';
Advisory GHSA-rw3w-jp96-pr74 · CVE CVE-2026-115464 · CVSS 7.1 High (v3.1), AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L · CWE CWE-862, CWE-863 · Fix 36878cc4 (4.4), 47a27581 (4.3), 3eaaf636 (4.2) · Affected >= 4.1.0, < 4.2.6, >= 4.3.0, < 4.3.5, >= 4.4.0, < 4.4.1 · Fixed 4.2.6, 4.3.5, 4.4.1
Reported 1 September · fixed the same day, with a CVE requested by the maintainer · released 4 September · advisory published 4 September, crediting me as reporter · CVE-2026-115464 assigned by GitHub 22 October · CVE record not yet live at the CVE Program at the time of writing.
Eurico Nicacio — @h3llh0und