Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) [GHSA-p7g9-rp3g-mgfg]

[Updated month year] Updated GHSA-p7g9-rp3g-mgfg

Overview

Broken Object Level Authorization (BOLA) vulnerabilities in API servers allow an attacker to access or manipulate data belonging to other users by simply changing object identifiers in requests. In Go with Gin, this often happens when a route reads an object identifier from the path, query, or body and returns the object without validating that the requestor owns or is authorized to view it. The impact ranges from data leakage to unauthorized updates or deletions, depending on the resource type and API surface. In real-world Gin code, endpoints such as GET /users/:id/profile, GET /orders/:id, or POST /resources/:id/actions commonly fetch data by id without an explicit ownership check. If the server trusts the client to supply a valid id and does not enforce that id against the authenticated user, any other user's resource becomes accessible. No CVEs are provided in this prompt. This class of vulnerability is particularly prevalent when simple object lookups are used instead of access-controlled queries. Mitigation involves enforcing strong object-level authorization at the service boundaries. Use the authenticated user context (from middleware) to verify ownership or access rights before returning data. Prefer database or ORM queries that filter by both id and owner_id, or implement a dedicated authorization middleware that rejects requests lacking proper rights. Combine with consistent error handling (403 Forbidden vs 404) and ensure logs capture auth failures for auditing. Tests should cover positive and negative cases for each endpoint, including scenarios where users attempt to access others' objects, and regression tests when adding new object types. Use Go test suites and CI pipelines to prevent new BOLA regressions.

Code Fix Example

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

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

type Item struct {
  ID int
  Data string
  OwnerID int
}

var items = map[int]Item{
  1: {ID: 1, Data: "Public data 1", OwnerID: 10},
  2: {ID: 2, Data: "Secret data 2", OwnerID: 20},
}

func getCurrentUserID(c *gin.Context) int {
  v := c.GetHeader("X-User-ID")
  id, _ := strconv.Atoi(v)
  return id
}

// Vulnerable: returns object if exists, regardless of ownership
func GetItemVuln(c *gin.Context) {
  id, err := strconv.Atoi(c.Param("id"))
  if err != nil {
     c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
     return
  }
  if item, ok := items[id]; ok {
     c.JSON(http.StatusOK, item)
     return
  }
  c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
}

// Fixed: checks ownership before returning
func GetItemFixed(c *gin.Context) {
  id, err := strconv.Atoi(c.Param("id"))
  if err != nil {
     c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
     return
  }
  userID := getCurrentUserID(c)
  if item, ok := items[id]; ok {
     if item.OwnerID != userID {
        c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
        return
     }
     c.JSON(http.StatusOK, item)
     return
  }
  c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
}

func main() {
  r := gin.Default()
  r.GET("/vuln/items/:id", GetItemVuln)
  r.GET("/fix/items/:id", GetItemFixed)
  r.Run(":8080")
}

CVE References

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