Overview
CVE-2026-33489 describes a broken access control scenario in CoreDNS where the transfer plugin selects the ACL rules based on a flawed matching function. Prior to 1.14.3, the longestMatch() implementation used a lexicographic comparison to pick a winning zone rule, rather than performing a true longest-suffix match. This caused a permissive parent-zone rule to override a more restrictive subzone rule depending on alphabetical ordering (for example comparing "example.org." and "a.example.org." lexically would prefer the parent). An unauthorized remote client could exploit this to perform AXFR/IXFR for a subzone and retrieve its full contents. The vulnerability maps to CWE-863 (Incorrect Authorization) and in the CVE context affected CoreDNS versions before 1.14.3. In a Go (Gin) service, a similar pattern can manifest when object-level access decisions are driven by a generic, order-based ACL rather than a proper object-specific authorization check. This guide shows how such BOLOA patterns can appear in Go services and how to remediate them. The core remediation is to implement correct, explicit, most-specific-rule evaluation rather than relying on fragile, lexicographic heuristics. The CoreDNS fix was released in 1.14.3 and should be used as a baseline example for robust per-object authorization logic in Go apps.
Affected Versions
CoreDNS < 1.14.3
Code Fix Example
Go (Gin) API Security Remediation
VULNERABLE pattern (Go/Gin):
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
type ZoneRule struct {
Zone string
Allow bool
}
var zoneRules = []ZoneRule{
{Zone: "example.org.", Allow: false}, // permissive parent rule
{Zone: "a.example.org.", Allow: true}, // restrictive subzone rule
}
// Vulnerable: uses lexicographic comparison to pick the winner, not the actual longest suffix
func vulnerableSelect(requested string) *ZoneRule {
var winner *ZoneRule
for i := range zoneRules {
if strings.HasSuffix(requested, zoneRules[i].Zone) {
if winner == nil || strings.Compare(zoneRules[i].Zone, winner.Zone) > 0 {
winner = &zoneRules[i]
}
}
}
return winner
}
// Fixed: uses true longest suffix match by comparing the length of the zone string
func fixedSelect(requested string) *ZoneRule {
var winner *ZoneRule
for i := range zoneRules {
if strings.HasSuffix(requested, zoneRules[i].Zone) {
if winner == nil || len(zoneRules[i].Zone) > len(winner.Zone) {
winner = &zoneRules[i]
}
}
}
return winner
}
func main() {
r := gin.Default()
r.GET("/transfer", func(c *gin.Context) {
zone := c.Query("zone")
// Vulnerable path (for demonstration):
v := vulnerableSelect(zone)
if v == nil || !v.Allow {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
c.String(http.StatusOK, "zone data for "+zone)
})
// To demonstrate fix, you could switch to fixedSelect(zone) in production
_ = r
}