Overview
The Kyverno CVE-2026-41323 disclosure demonstrates how a component can leak tokens and perform SSRF when it uncritically trusts a service URL and an attached token. The admission controller token, when combined with an unvalidated outgoing URL, enabled attackers to reach attacker-controlled servers and potentially patch webhook configurations, leading to full cluster compromise. The underlying risk combines information disclosure (CWE-200) with SSRF (CWE-918) and, ultimately, broken access control if an attacker can leverage the stolen token to access unauthorized resources. This real-world failure pattern maps to Broken Object Property Level Authorization in that insufficient checks on which resources a caller can access allows adversaries to operate beyond their intended scope, especially when object-level ownership data could be inferred or bypassed. When ported to Go (Gin) in API backends, similar misconfigurations manifest as endpoints that return or mutate resources solely based on identifiers supplied by the client without confirming the requester’s rights to that specific object.
Affected Versions
Kyverno: vulnerable before 1.18.0-rc1 (1.18 line); before 1.17.2-rc1 (1.17 line); before 1.16.4 (1.16 line).
Code Fix Example
Go (Gin) API Security Remediation
Vulnerable pattern (Go + Gin):
package main
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type Resource struct {
ID int64
OwnerID int64
Data string
}
var resources = []Resource{{ID: 1, OwnerID: 100, Data: "secret"}}
func main() {
r := gin.Default()
// Mock authentication: userID is 100
r.Use(func(c *gin.Context) {
c.Set("userID", int64(100))
c.Next()
})
// Vulnerable: returns resource without ownership check
r.GET("/vuln/resources/:id", func(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
var res *Resource
for i := range resources {
if resources[i].ID == id {
res = &resources[i]
break
}
}
if res == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
c.JSON(http.StatusOK, gin.H{"id": res.ID, "data": res.Data})
})
// Vulnerable: update without ownership check
r.PUT("/vuln/resources/:id", func(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
var res *Resource
for i := range resources {
if resources[i].ID == id {
res = &resources[i]
break
}
}
if res == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
res.Data = "updated"
c.JSON(http.StatusOK, gin.H{"id": res.ID, "data": res.Data})
})
// Fixed: ownership check implemented below using same routes
r.Run()
}
// Fixed pattern (same file) with explicit ownership checks:
//
// r.GET("/fix/resources/:id", ...) and r.PUT("/fix/resources/:id", ...) should verify that the
// authenticated user owns the resource before returning/modifying it. See also the
// CVE-2026-41323 reference for the type of impact when object-level authorization is not enforced.
package main
// ... same imports and types as above ...
func mainFixed() {
r := gin.Default()
// Mock authentication: userID is 100
r.Use(func(c *gin.Context) {
c.Set("userID", int64(100))
c.Next()
})
r.GET("/fix/resources/:id", func(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
var res *Resource
for i := range resources {
if resources[i].ID == id {
res = &resources[i]
break
}
}
if res == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if v, ok := c.Get("userID"); ok {
if uid, ok := v.(int64); ok && res.OwnerID != uid {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
} else {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthenticated"})
return
}
c.JSON(http.StatusOK, gin.H{"id": res.ID, "data": res.Data})
})
r.PUT("/fix/resources/:id", func(c *gin.Context) {
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
var res *Resource
for i := range resources {
if resources[i].ID == id {
res = &resources[i]
break
}
}
if res == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if v, ok := c.Get("userID"); ok {
if uid, ok := v.(int64); ok && res.OwnerID != uid {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
} else {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthenticated"})
return
}
res.Data = "updated"
c.JSON(http.StatusOK, gin.H{"id": res.ID, "data": res.Data})
})
r.Run()
}