Overview
Broken Object Property Level Authorization leaks data and enables horizontal privilege escalation when a user can access or manipulate resources by altering object identifiers in requests. In real-world apps, attackers may guess or enumerate IDs and read, modify, or delete assets they should not own, leading to data leakage, regulatory risk, and loss of trust.
In Go with the Gin framework, this vulnerability often appears when a handler retrieves a resource solely by ID from the URL and returns it without verifying ownership. It is common to bind path parameters, fetch via an ORM by primary key, and rely on the object existing to imply authorization, which is unsafe.
Even with authentication present, object-level checks are frequently missing or performed only at a coarse level. Attackers can bypass checks by supplying arbitrary IDs, risking privacy and data integrity. Remediation requires enforcing ownership in the data query or via middleware that ties the current user to the fetched object, across all related endpoints.
No CVE IDs are provided here; this pattern is a well-known risk across languages and frameworks. Apply consistent, explicit authorization at the API boundary and ensure data-layer checks reflect the actual owner before returning sensitive fields or mutating state.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Resource struct {
ID string `json:"id"`
OwnerID string `json:"owner_id"`
Data string `json:"data"`
}
var resources = map[string]Resource{
"a1": {ID: "a1", OwnerID: "u1", Data: "Secret A"},
"a2": {ID: "a2", OwnerID: "u2", Data: "Secret B"},
}
func main() {
r := gin.Default()
// Vulnerable pattern: no authorization check when fetching by ID
r.GET("/vulnerable/resources/:id", vulnerableGetResource)
// Fixed pattern: enforce ownership before returning the object
r.GET("/resources/:id", secureGetResource)
r.Run(":8080")
}
// helper: extract user identity from request (simplified)
func getUserIDFromHeader(c *gin.Context) string {
return c.GetHeader("X-User-ID")
}
// Vulnerable: returns resource by ID without ownership check
func vulnerableGetResource(c *gin.Context) {
id := c.Param("id")
if res, ok := resources[id]; ok {
c.JSON(http.StatusOK, res)
return
}
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
}
// Secure: verify ownership before returning the resource
func secureGetResource(c *gin.Context) {
id := c.Param("id")
userID := getUserIDFromHeader(c)
if res, ok := resources[id]; ok {
if res.OwnerID != userID || userID == "" {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
c.JSON(http.StatusOK, res)
return
}
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
}