Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-8wfp-579w-6r25]

[Updated Apr 2026] Updated GHSA-8wfp-579w-6r25

Overview

Broken Object Property Level Authorization (BOLA) vulnerabilities happen when a API returns an object or part of it without conclusively verifying that the requester is allowed to access that specific object. In Go with Gin, handlers often fetch a resource by id and serialize it back to the client without checking ownership against the authenticated user. Attackers can enumerate IDs and retrieve data owned by other users, or perform actions on resources they do not own if object fields are used in authorization decisions rather than explicit checks. This pattern tends to leak personal or sensitive data across tenants, leads to credential stuffing of object IDs, and enables escalation of permissions when a single miss in a single endpoint is enough to break isolation. In production, BOLA risks arise when the API trusts the client to provide ownership or when audit trails rely on object properties rather than robust authorization checks. This commonly affects endpoints that return documents, profiles, or configurations, and can be exacerbated by mass-join queries that omit ownership constraints. In Gin-based services, ensure that every endpoint that returns a resource also asserts that the requester's identity matches the object's OwnerID (or is allowed by RBAC), rather than trusting the object's fields for access control. Testing should include cross-tenant validation and fuzz cases that attempt to access another user's data via known IDs; adoption of explicit authorization middleware helps prevent these bugs.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Document struct {
  ID string
  OwnerID string
  Content string
}

var db = map[string]Document{
  "doc1": {ID: "doc1", OwnerID: "alice", Content: "secret"},
}

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

  // Vulnerable pattern: returns doc without ownership check
  r.GET("/docs/:id", func(c *gin.Context) {
    id := c.Param("id")
    if d, ok := db[id]; ok {
      c.JSON(http.StatusOK, d)
      return
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
  })

  // Fixed pattern: enforce ownership before returning
  r.GET("/docs-fixed/:id", func(c *gin.Context) {
    id := c.Param("id")
    if d, ok := db[id]; ok {
      user := c.GetHeader("X-User")
      if d.OwnerID != user {
        c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
        return
      }
      c.JSON(http.StatusOK, d)
      return
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
  })

  r.Run()
}

CVE References

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