Overview
Broken object property level authorization occurs when API endpoints rely on a client-supplied object identifier (for example, /profiles/:id) to grant access or perform updates without verifying the caller's rights to that specific object. In Go with Gin, handlers that fetch a resource by ID and return it can become vulnerable if ownership checks are omitted or inconsistent. This class of vulnerability enables attackers to enumerate IDs and access data they should not own, leading to data exposure and privacy violations. Real-world impact includes leaked personal information, sensitive assets, or improperly overwritten resources across users or tenants. If write paths are exposed, attackers may tamper with or delete others' data, causing business disruption and reputational damage. In Gin-based services, improper per-object checks often arise from patterns where a handler reads an ID from the URL and returns the associated resource without confirming ownership or required permissions.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Profile struct {
ID string
OwnerID string
}
var profiles = map[string]Profile{
"1": {ID: "1", OwnerID: "user-123"},
"2": {ID: "2", OwnerID: "user-999"},
}
func main() {
r := gin.Default()
r.Use(mockAuthMiddleware())
// Vulnerable: returns resource by id without authorization check
r.GET("/vuln/profiles/:id", getProfileVuln)
// Fixed: enforces per-resource ownership before returning data
r.GET("/fixed/profiles/:id", getProfileFixed)
r.Run(":8080")
}
func mockAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
userID := c.GetHeader("X-User-ID")
if userID == "" {
// Unauthorized without user context
c.AbortWithStatus(http.StatusUnauthorized)
return
}
c.Set("currentUserID", userID)
c.Next()
}
}
func getProfileVuln(c *gin.Context) {
id := c.Param("id")
if p, ok := profiles[id]; ok {
c.JSON(http.StatusOK, p)
return
}
c.Status(http.StatusNotFound)
}
func getProfileFixed(c *gin.Context) {
id := c.Param("id")
if p, ok := profiles[id]; ok {
if current, exists := c.Get("currentUserID"); exists {
if current.(string) == p.OwnerID {
c.JSON(http.StatusOK, p)
return
}
c.Status(http.StatusForbidden)
return
}
c.Status(http.StatusUnauthorized)
return
}
c.Status(http.StatusNotFound)
}