Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) [Mar 2026] [GHSA-g2j9-7rj2-gm6c]

[Fixed Mar 2026] Updated GHSA-g2j9-7rj2-gm6c

Overview

Broken Object Level Authorization (BOLA) allows attackers to access, modify, or delete specific resources by altering an object identifier in a request. In Go apps using Gin, endpoints that fetch a resource by ID can return data belonging to other users if ownership is not verified. The impact can include data leakage, confidentiality breaches, and regulatory penalties when sensitive records are exposed through poorly secured APIs. In Gin-based services, authentication at the route level is common, but per-object authorization is often missing. If a handler retrieves a resource by ID and returns it without checking that the requesting user owns it, an attacker can enumerate IDs or craft calls to access unauthorized objects. This pattern enables horizontal privilege escalation and can affect any resource with an identifiable ID. Mitigation requires enforcing ownership or policy checks for every object-accessing endpoint. Apply authorization in the service layer or a middleware by comparing resource.OwnerID (or a similar attribute) with the authenticated user's ID, or by using RBAC/ABAC policy engines. Return 403 for unauthorized requests and avoid leaking whether a resource exists when access is denied, to reduce information leakage. Testing and verification are essential: write unit tests for owner vs non-owner access, add integration tests with realistic tokens, and perform end-to-end checks. Use deny-by-default, centralize authorization logic, and review code changes for new object-level checks to prevent regressions.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Resource struct {
  ID int
  OwnerID int
  Data string
}

var resources = []Resource{
  {ID:1, OwnerID:42, Data:"secret A"},
  {ID:2, OwnerID:99, Data:"secret B"},
}

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

  // simple auth: user id from header
  r.Use(func(c *gin.Context){
    if uidStr := c.GetHeader("X-User-Id"); uidStr != "" {
      if uid, err := strconv.Atoi(uidStr); err == nil {
        c.Set("userID", uid)
      }
    }
    c.Next()
  })

  // Vulnerable: returns resource by id without ownership check
  r.GET("/resources/:id", func(c *gin.Context){
    id, _ := strconv.Atoi(c.Param("id"))
    for _, res := range resources {
      if res.ID == id {
        c.JSON(http.StatusOK, res)
        return
      }
    }
    c.Status(http.StatusNotFound)
  })

  // Fixed: enforce ownership check
  r.GET("/secure/resources/:id", func(c *gin.Context){
    id, _ := strconv.Atoi(c.Param("id"))
    var res *Resource
    for i := range resources {
      if resources[i].ID == id {
        res = &resources[i]
        break
      }
    }
    if res == nil {
      c.Status(http.StatusNotFound)
      return
    }
    if u, ok := c.Get("userID"); ok {
      userID := u.(int)
      if res.OwnerID != userID {
        c.Status(http.StatusForbidden)
        return
      }
      c.JSON(http.StatusOK, res)
      return
    }
    c.Status(http.StatusUnauthorized)
  })

  r.Run(":8080")
}

CVE References

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