Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) - CVE-2026-33489 [CVE-2026-33489]

[Updated June 2026] Updated CVE-2026-33489

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
}

CVE References

Choose which optional cookies to allow. You can change this any time.