Overview
Remediation guide for Broken Object Property Level Authorization in Go (Gin). This vulnerability occurs when an API returns object data based solely on an object ID supplied by the client without validating that the caller owns or is permitted to access that specific object. In Go with Gin, this pattern often appears in handlers that fetch a resource by ID and return the object fields directly, effectively using the ID as the only access gate. Attackers can enumerate or guess IDs to retrieve data belonging to other users, leading to unintended data exposure. The risk is particularly acute when sensitive fields or nested properties (for example, owner identifiers, private attributes, or aggregated data) are included in the response and not gated by proper authorization checks.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type Resource struct {
ID int64 `json:"id"`
OwnerID int64 `json:"owner_id"`
Data string `json:"data"`
}
var resources = []Resource{
{ID: 1, OwnerID: 101, Data: "secret A"},
{ID: 2, OwnerID: 202, Data: "secret B"},
}
func dbGetResourceByID(id string) (Resource, bool) {
for _, r := range resources {
if strconv.FormatInt(r.ID, 10) == id {
return r, true
}
}
return Resource{}, false
}
// Vulnerable pattern: returns the resource without verifying ownership
func vulnerableHandler(c *gin.Context) {
// Simulated authenticated user (for demonstration purposes)
userID := int64(0)
if v, ok := c.Get("userID"); ok {
userID = v.(int64)
}
id := c.Param("id")
res, ok := dbGetResourceByID(id)
if !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
// No ownership check: this exposes resources owned by others
_ = userID
c.JSON(http.StatusOK, res)
}
// Fixed pattern: enforce object-level authorization
func secureHandler(c *gin.Context) {
userID := int64(0)
if v, ok := c.Get("userID"); ok {
userID = v.(int64)
}
id := c.Param("id")
res, ok := dbGetResourceByID(id)
if !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if res.OwnerID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
c.JSON(http.StatusOK, res)
}
func main() {
r := gin.Default()
// Simple fake authentication to illustrate per-request ownership
r.Use(func(c *gin.Context) {
// In real apps, extract from JWT or session; here we set a fixed user for demo
c.Set("userID", int64(101))
c.Next()
})
r.GET("/vuln/resource/:id", vulnerableHandler)
r.GET("/secure/resource/:id", secureHandler)
r.Run(":8080")
}