Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-99j6-hj87-6fcf]

[Updated Apr 2026] Updated GHSA-99j6-hj87-6fcf

Overview

Note: No CVE IDs are provided for this guide. Broken Object Property Level Authorization (BOPLA) occurs when an API authenticates a user but fails to verify that the user is allowed to access a specific object. In Go applications using the Gin framework, endpoints that read an object ID from the path or request body must enforce per-object permissions; otherwise, an authenticated user could access, modify, or delete resources that belong to others. Without object-level checks, the mere presence of a valid session can become the authorization boundary, leading to data exposure and integrity risks. Real-world impact includes leaking sensitive data, improper edits, or deletions across user boundaries, especially in multi-tenant or partner-integrated systems.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Resource struct {
  ID      int64
  OwnerID int64
  Data    string
}

var resources = map[int64]*Resource{
  1: {ID: 1, OwnerID: 2, Data: "Alice data"},
  2: {ID: 2, OwnerID: 3, Data: "Bob data"},
}

func fetchResource(id int64) *Resource {
  if r, ok := resources[id]; ok {
    return r
  }
  return nil
}

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

  // Vulnerable pattern: fetch and return without per-object authorization
  r.GET("/vuln/resources/:id", func(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
      return
    }
    if res := fetchResource(id); res != nil {
      c.JSON(http.StatusOK, res)
      return
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
  })

  // Fixed pattern: enforce per-object authorization
  r.Use(mockAuthMiddleware()) // populate c.Set("userID") and c.Set("isAdmin") for demonstration
  r.GET("/resources/:id", func(c *gin.Context) {
    id, err := strconv.ParseInt(c.Param("id"), 10, 64)
    if err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
      return
    }
    res := fetchResource(id)
    if res == nil {
      c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
      return
    }
    uidVal, exists := c.Get("userID")
    if !exists {
      c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
      return
    }
    uid, _ := uidVal.(int64)
    if res.OwnerID != uid && !isAdmin(c) {
      c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
      return
    }
    c.JSON(http.StatusOK, res)
  })

  r.Run(":8080")
}

func mockAuthMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    if id := c.GetHeader("X-User-ID"); id != "" {
      if parsed, err := strconv.ParseInt(id, 10, 64); err == nil {
        c.Set("userID", parsed)
      }
    }
    if c.GetHeader("X-Admin") == "1" {
      c.Set("isAdmin", true)
    }
  }
}

func isAdmin(c *gin.Context) bool {
  if v, ok := c.Get("isAdmin"); ok {
    if b, ok := v.(bool); ok {
      return b
    }
  }
  return false
}

CVE References

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