Broken Object Level Authorization

BOLOA in Go (Gin): Broken Object Level Authorization [CVE-2026-44380]

[Updated May 2026] Updated CVE-2026-44380

Overview

Broken Object Level Authorization (BOLOA) vulnerabilities occur when an app fails to enforce per-resource permissions, allowing an attacker with broad or organizational-level access to perform actions on specific resources owned by other users. The CVE-2026-44380 example from MISP demonstrates this: prior to 2.5.37, an authenticated organization administrator could reset authentication keys for site administrator accounts within the same organization because the flow did not explicitly prevent access to higher-privilege targets. Although MISP is not implemented in Go, the underlying flaw-incorrect authorization checks that permit cross-privilege operations-maps directly to Go services built with Gin. In such cases, an org admin might reset a site admin key if the target shares the same org, enabling privilege escalation. This is classified under CWE-863 (Incorrect Authorization). The fix is to enforce per-resource authorization rules, not just coarse role or org membership, and to clearly separate permissions between roles so high-privilege accounts cannot be manipulated by lower-privilege actors within the same organization.

Affected Versions

Prior to 2.5.37

Code Fix Example

Go (Gin) API Security Remediation
VULNERABLE:
package main

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

type User struct {
  ID    string
  OrgID string
  Role  string // e.g., "OrgAdmin", "SiteAdmin", "SystemAdmin"
  Key   string
}

var users = map[string]*User{
  "u1": {ID: "u1", OrgID: "org1", Role: "SiteAdmin", Key: "orig-site"},
  "u2": {ID: "u2", OrgID: "org1", Role: "OrgAdmin", Key: "orig-org"},
  "u3": {ID: "u3", OrgID: "org2", Role: "SiteAdmin", Key: "orig-site2"},
}

func getUserFromHeader(c *gin.Context) *User {
  id := c.GetHeader("X-User-Id")
  if u, ok := users[id]; ok {
    return u
  }
  return nil
}

func generateKey() string {
  // simplistic placeholder for demonstration
  return "new-key-" + RandStringRunes(8)
}

func vulnerableResetAuthKey(c *gin.Context) {
  requester := getUserFromHeader(c)
  targetID := c.Param("id")
  target := users[targetID]
  if requester == nil || target == nil {
    c.Status(http.StatusBadRequest)
    return
  }
  // VULNERABLE CHECK: only ensures same OrgID; no per-role protection
  if requester.OrgID != target.OrgID {
    c.Status(http.StatusForbidden)
    return
  }
  // No check on target.Role; any admin in the same org can reset keys for any target
  target.Key = generateKey()
  c.JSON(http.StatusOK, gin.H{"target_id": target.ID, "new_key": target.Key})
}

func main() {
  r := gin.Default()
  r.POST("/vulnerable/users/:id/reset-key", vulnerableResetAuthKey)
  // The fixed handler is shown in the FIX section below
  r.Run(":8080")
}

FIX:
package main

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

type User struct {
  ID    string
  OrgID string
  Role  string // e.g., "OrgAdmin", "SiteAdmin", "SystemAdmin"
  Key   string
}

var users = map[string]*User{
  "u1": {ID: "u1", OrgID: "org1", Role: "SiteAdmin", Key: "orig-site"},
  "u2": {ID: "u2", OrgID: "org1", Role: "OrgAdmin", Key: "orig-org"},
  "u3": {ID: "u3", OrgID: "org2", Role: "SiteAdmin", Key: "orig-site2"},
}

func getUserFromHeader(c *gin.Context) *User {
  id := c.GetHeader("X-User-Id")
  if u, ok := users[id]; ok {
    return u
  }
  return nil
}

func generateKey() string {
  return "new-key-" + RandStringRunes(8)
}

func fixedResetAuthKey(c *gin.Context) {
  requester := getUserFromHeader(c)
  targetID := c.Param("id")
  target := users[targetID]
  if requester == nil || target == nil {
    c.Status(http.StatusBadRequest)
    return
  }
  // FIX: enforce per-resource authorization
  // If the target is a SiteAdmin, only SystemAdmin can reset
  if target.Role == "SiteAdmin" && requester.Role != "SystemAdmin" {
    c.Status(http.StatusForbidden)
    return
  }
  // Also enforce org scoping
  if requester.OrgID != target.OrgID {
    c.Status(http.StatusForbidden)
    return
  }
  target.Key = generateKey()
  c.JSON(http.StatusOK, gin.H{"target_id": target.ID, "new_key": target.Key})
}

func main() {
  r := gin.Default()
  r.POST("/fixed/users/:id/reset-key", fixedResetAuthKey)
  r.Run(":8080")
}

// Helpers
import (
  "math/rand"
  "time"
)

func RandStringRunes(n int) string {
  var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
  b := make([]rune, n)
  for i := range b {
    b[i] = letterRunes[rand.Intn(len(letterRunes))]
  }
  return string(b)
}

func init() {
  rand.Seed(time.Now().UnixNano())
}

CVE References

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