Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-7h3j-592v-jcrp]

[Updated Sep 2026] Updated GHSA-7h3j-592v-jcrp

Overview

Broken Object Property Level Authorization (also called broken access control at the object level) allows an authenticated user to access, modify, or delete resources that belong to another user by manipulating identifiers in requests. In Gin-based Go services, this often occurs when routes accept an object ID (for example, /resources/:id) and directly fetch or expose the object without validating ownership or permission. The impact can include data leakage, privacy violations, and unauthorized actions that undermine trust, regulatory compliance, and business risk. In Go (Gin) these flaws typically manifest when handlers perform a read or write using c.Param("id") or extracting an ID from the request body, then return or mutate the resource after a basic existence check but without verifying that the caller is authorized for that resource. Because Gin provides no implicit permission model, authorization must be explicit in code or via middleware, ideally by combining the current user identity with the resource owner or an RBAC policy in the DB query or service layer. Remediation involves enforcing object-scoped authorization at the boundary: verify ownership or permission before returning data or performing mutations. Prefer database queries that include owner_id or access rights, implement middleware or helper functions to load the resource and check access, and adopt RBAC/ABAC as appropriate. Add tests that simulate access by multiple users to the same objects and ensure forbidden responses on violations.

Code Fix Example

Go (Gin) API Security Remediation
package main\n\nimport (\n  \"net/http\"\n  \"github.com/gin-gonic/gin\"\n)\n\ntype Resource struct {\n  ID string\n  OwnerID string\n  Data string\n}\n\nvar resources = map[string]Resource{\n  \"1\": {ID: \"1\", OwnerID: \"userA\", Data: \"secret\"},\n  \"2\": {ID: \"2\", OwnerID: \"userB\", Data: \"public\"},\n}\n\nfunc main() {\n  r := gin.Default()\n  // Vulnerable pattern: no per-object authorization\n  r.GET(\"/vuln/resources/:id\", vulnGetResource)\n  // Fixed pattern: enforce object-level authorization\n  r.GET(\"/fix/resources/:id\", fixedGetResource)\n  r.Run(\":8080\")\n}\n\nfunc getCurrentUserID(c *gin.Context) string {\n  // In real apps, extract from JWT/session. Here we simulate via context value.\n  if v, exists := c.Get(\"userID\"); exists {\n    if s, ok := v.(string); ok {\n      return s\n    }\n  }\n  return \"\"\n}\n\n// Vulnerable: returns resource without checking ownership\nfunc vulnGetResource(c *gin.Context) {\n  id := c.Param(\"id\")\n  res, ok := resources[id]\n  if !ok {\n    c.JSON(http.StatusNotFound, gin.H{\"error\": \"not found\"})\n    return\n  }\n  c.JSON(http.StatusOK, gin.H{\"id\": res.ID, \"owner\": res.OwnerID, \"data\": res.Data})\n}\n\n// Fixed: enforce object-level authorization by checking ownership\nfunc fixedGetResource(c *gin.Context) {\n  id := c.Param(\"id\")\n  userID := getCurrentUserID(c)\n  res, ok := resources[id]\n  if !ok {\n    c.JSON(http.StatusNotFound, gin.H{\"error\": \"not found\"})\n    return\n  }\n  if res.OwnerID != userID {\n    c.JSON(http.StatusForbidden, gin.H{\"error\": \"forbidden\"})\n    return\n  }\n  c.JSON(http.StatusOK, gin.H{\"id\": res.ID, \"owner\": res.OwnerID, \"data\": res.Data})\n}\n

CVE References

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