Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-fq2j-j8hc-8vw8]

[Updated Mar 2026] Updated GHSA-fq2j-j8hc-8vw8

Overview

Broken Object Property Level Authorization (BOPLA) vulnerabilities occur when an API trusts an object ID from the client and returns the corresponding resource without validating that the current user is allowed to access it. In Go with Gin, endpoints frequently take a resource ID from the URL (for example, /items/:id) and fetch the item directly, or output a 200 response before verifying ownership. Attackers can enumerate IDs to access or modify others’ data, leading to data leakage, privacy violations, and potential privilege escalation in multi-tenant or partner integrations. In Go (Gin), this pattern manifests when route handlers perform a database lookup by ID and return the result without cross-checking against the authenticated user’s permissions. Common mistakes include performing a single ownership check during a lower-level service call or relying solely on authentication middleware for access decisions. Without explicit per-resource authorization, any authenticated user may access or alter any resource they know the ID for. Impact is significant for user data, financial records, and regulatory compliance. It enables attackers to read, delete, or modify resources belonging to others, escalate access to privileged actions, and undermine trust in multi-tenant platforms. Detection often involves auditing for endpoints that expose object-level access and verifying whether authorization checks depend on a user context that is loaded after the resource is fetched. To remediate, implement explicit ownership or policy checks for each object-level endpoint, avoid returning resources before authorization, and centralize access control logic. Use strong checks in Gin handlers or middleware, and consider a policy engine or role-based access control (RBAC) to manage permissions.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Item struct {
  ID int `json:\"id\"`
  OwnerID int `json:\"owner_id\"`
  Data string `json:\"data\"`
}

var items = map[int]Item{
  1: {ID: 1, OwnerID: 1, Data: "Secret A"},
  2: {ID: 2, OwnerID: 2, Data: "Secret B"},
}

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

  // Simple auth middleware: require X-User-ID header
  r.Use(func(c *gin.Context) {
    u := c.GetHeader("X-User-ID")
    if u == "" {
      c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
      return
    }
    id, err := strconv.Atoi(u)
    if err != nil {
      c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
      return
    }
    c.Set("userID", id)
    c.Next()
  })

  // Vulnerable endpoint (no ownership check)
  r.GET("/vulnerable/items/:id", vulnerableHandler)
  // Fixed endpoint (ownership check implemented)
  r.GET("/fixed/items/:id", fixedHandler)

  r.Run(":8080")
}

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

func fixedHandler(c *gin.Context) {
  id, err := strconv.Atoi(c.Param("id"))
  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
    return
  }
  item, ok := items[id]
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
    return
  }
  userID, exists := c.Get("userID")
  if !exists {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  if item.OwnerID != userID.(int) {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }
  c.JSON(http.StatusOK, item)
}

CVE References

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