Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-xgxp-f695-6vrp]

[Updated Sep 2026] Updated GHSA-xgxp-f695-6vrp

Overview

Broken Object Property Level Authorization (BOPLA) vulnerabilities occur when an API gates access to sensitive data based on a property that the client can influence, rather than strictly enforcing ownership on the actual resource. In practice, endpoints may accept an object property such as owner_id from the request and use it to decide whether to reveal a field or perform an action. If the server trusts this client-supplied value, an attacker can manipulate which object attributes they are allowed to read or modify, leaking data across tenants or escalating privileges. In Go applications built with Gin, this often shows up in routes that fetch an object by ID and then gate access to a nested property or sensitive field via a client-controlled property. For example, the code may require a request field owner_id to match the resource's OwnerID, or use owner_id to filter results rather than verifying that the authenticated user owns the resource. The impact is data leakage, privacy violations, and regulatory risk. Remediation emphasizes explicit ownership checks against the authenticated user's identity and avoiding reliance on client-supplied values for authorization decisions. In Gin, you should retrieve the resource, confirm that resource.OwnerID equals the user's ID from your JWT/session, and only then return sensitive properties. Centralize authorization logic (middleware or policy engine) and complement with thorough tests that cover cross-tenant access scenarios.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Document struct {
  ID      int
  OwnerID int
  Content string
  Secret  string
}

var documents = map[string]Document{
  "1": {ID: 1, OwnerID: 10, Content: "Public", Secret: "TopSecretA"},
  "2": {ID: 2, OwnerID: 20, Content: "Public", Secret: "TopSecretB"},
}

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

  // Mock auth middleware: in a real app, populate from JWT/session
  r.Use(func(c *gin.Context) {
    c.Set("userID", 10)
    c.Next()
  })

  r.GET("/docs/:id/vuln", vulnerableHandler)
  r.GET("/docs/:id/fix", fixedHandler)

  r.Run(":8080")
}

func vulnerableHandler(c *gin.Context) {
  id := c.Param("id")
  doc, ok := documents[id]
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
    return
  }

  var req struct {
    OwnerID int `json:"owner_id"`
  }
  _ = c.BindJSON(&req)

  // Vulnerable: uses client-provided OwnerID to gate access
  if req.OwnerID != doc.OwnerID {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"secret": doc.Secret})
}

func fixedHandler(c *gin.Context) {
  id := c.Param("id")
  doc, ok := documents[id]
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
    return
  }

  userIDVal, exists := c.Get("userID")
  if !exists {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  userID := userIDVal.(int)

  // Fixed: verify ownership against authenticated user, not client-provided data
  if doc.OwnerID != userID {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"secret": doc.Secret})
}

CVE References

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