Broken Object Level Authorization

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

[Updated Mar 2026] Updated CVE-2026-33132

Overview

Broken Object Level Authorization (BOLOA) vulnerabilities like CVE-2026-33132 can have real-world impact in multi-tenant systems. Zitadel’s flaw allowed bypassing organization enforcement during authentication for certain flows, enabling sign-ins from users outside an enforced organization when device authorization and login V2/OIDC API endpoints were not properly protected. Although the core issue relates to authentication-time org checks, the bypass could lead to unintended access to objects, data, or resources scoped to an organization. This highlights why BOLOA is critical: if you fail to strictly tie authorization decisions to a trusted org context, attackers can access or manipulate resources across tenants even when authentication succeeds. The CVE explicitly ties to CWE-863, and Zitadel patched the issue in 3.4.9 and 4.12.3, underscoring the need to apply consistent, endpoint-wide org-scoping checks in any Go (Gin) API that enforces organization context across authentication and resource access. In Go applications using Gin, similar patterns can reproduce the flaw if endpoints rely on user-supplied or URL-based org context without validating that the resource’s org matches the authenticated user’s org. Implementing robust, centralized object-level authorization is essential to prevent cross-tenant access. The mitigation remains applicable regardless of the exact framework, but the fix must be codified in Go/Gin handlers, middleware, and tests.

Affected Versions

3.x prior to 3.4.9; 4.0.0 through 4.12.2

Code Fix Example

Go (Gin) API Security Remediation
Vulnerable pattern:
package main

import (
  "net/http"
  "github.com/gin-gonic/gin"
)

type Resource struct {
  ID    string
  OrgID string
  Data  string
}

var resources = map[string]Resource{
  "r1": {ID: "r1", OrgID: "org1", Data: "secret1"},
  "r2": {ID: "r2", OrgID: "org2", Data: "secret2"},
}

type User struct {
  ID    string
  OrgID string
}

func AuthMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    // In a real app, parse JWT/OAuth token here and extract OrgID from claims
    // For demonstration, we simulate with a header
    org := c.GetHeader("X-Org-Id")
    if org == "" {
      c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
      return
    }
    c.Set("user", &User{ID: "user-1", OrgID: org})
    c.Next()
  }
}

func vulnerableGetResource(c *gin.Context) {
  // Vulnerable: does not enforce org scoping, trusts path params only for lookup
  id := c.Param("id")
  if res, ok := resources[id]; ok {
    c.JSON(http.StatusOK, res) // returns resource regardless of org
    return
  }
  c.Status(http.StatusNotFound)
}

func fixedGetResource(c *gin.Context) {
  userI, _ := c.Get("user")
  user := userI.(*User)
  orgFromPath := c.Param("org_id")
  id := c.Param("id")
  if res, ok := resources[id]; ok {
    if res.OrgID != orgFromPath || user.OrgID != orgFromPath {
      c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
      return
    }
    c.JSON(http.StatusOK, res)
    return
  }
  c.Status(http.StatusNotFound)
}

func main() {
  r := gin.Default()
  r.Use(AuthMiddleware())

  // Vulnerable route (to illustrate the flaw in a controlled demo)
  r.GET("/orgs/:org_id/resources/:id", vulnerableGetResource)

  // Fixed route enforcing BOLOA across the endpoint
  r.GET("/secure/orgs/:org_id/resources/:id", fixedGetResource)

  r.Run(":8080")
}

=== Fixed pattern ===
package main

import (
  "net/http"
  "github.com/gin-gonic/gin"
)

type Resource struct {
  ID    string
  OrgID string
  Data  string
}

var resources = map[string]Resource{
  "r1": {ID: "r1", OrgID: "org1", Data: "secret1"},
  "r2": {ID: "r2", OrgID: "org2", Data: "secret2"},
}

type User struct {
  ID    string
  OrgID string
}

func AuthMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    org := c.GetHeader("X-Org-Id")
    if org == "" {
      c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
      return
    }
    c.Set("user", &User{ID: "user-1", OrgID: org})
    c.Next()
  }
}

func fixedGetResource(c *gin.Context) {
  userI, _ := c.Get("user")
  user := userI.(*User)
  orgFromPath := c.Param("org_id")
  id := c.Param("id")
  if res, ok := resources[id]; ok {
    // Enforce object-level authorization: the resource must belong to the requested org,
    // and the user must belong to that org as well
    if res.OrgID != orgFromPath || user.OrgID != orgFromPath {
      c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
      return
    }
    c.JSON(http.StatusOK, res)
    return
  }
  c.Status(http.StatusNotFound)
}

func main() {
  r := gin.Default()
  r.Use(AuthMiddleware())
  r.GET("/secure/orgs/:org_id/resources/:id", fixedGetResource)
  r.Run(":8080")
}

CVE References

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