Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-h2jq-g4cq-5ppq]

[Updated April 2026] Updated GHSA-h2jq-g4cq-5ppq

Overview

Broken Object Property Level Authorization vulnerabilities in Go web apps like Gin allow attackers to access or manipulate resources that should be owned by another user simply by supplying an ID in the request. In production, this can leak sensitive content, enable account takeover, or reveal data from many users when endpoints do not verify the resource owner against the authenticated user. The vulnerability typically manifests when an endpoint reads an object by ID and returns it without validating ownership or applying a tenant scope. In Gin, a handler might fetch a resource using the ID from the URL and return it without checking that the current user ID equals resource.OwnerID. Remediation focuses on enforcing strict ownership checks, scoping queries by owner_id, and returning 403/404 as appropriate. Implement authentication middleware to establish identity, perform owner checks in handlers, and prefer parameterized queries or in-memory validation.

Code Fix Example

Go (Gin) API Security Remediation
Vulnerable pattern:
```go
import (
  "net/http"
  "strconv"

  "github.com/gin-gonic/gin"
)

type Resource struct { ID int64; OwnerID int64; Data string }

var resources = []Resource{
  {ID: 1, OwnerID: 10, Data: `secretA`},
  {ID: 2, OwnerID: 42, Data: `secretB`},
}

func findResourceByID(id int64) (Resource, bool) {
  for _, r := range resources {
    if r.ID == id { return r, true }
  }
  return Resource{}, false
}

func vulnerableGetResource(c *gin.Context) {
  idStr := c.Param("id")
  id, _ := strconv.ParseInt(idStr, 10, 64)
  if r, ok := findResourceByID(id); ok {
    c.JSON(http.StatusOK, r)
    return
  }
  c.Status(http.StatusNotFound)
}

func authMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) { c.Set("userID", int64(42)); c.Next() }
}

func fixedGetResource(c *gin.Context) {
  uidVal, ok := c.Get("userID")
  if !ok { c.Status(http.StatusUnauthorized); return }
  userID := uidVal.(int64)

  idStr := c.Param("id")
  id, _ := strconv.ParseInt(idStr, 10, 64)
  r, found := findResourceByID(id)
  if !found { c.Status(http.StatusNotFound); return }
  if r.OwnerID != userID { c.Status(http.StatusForbidden); return }
  c.JSON(http.StatusOK, r)
}

func main() {
  r := gin.Default()
  r.GET("/vulnerable/resources/:id", vulnerableGetResource)
  rG := r.Group("/")
  rG.Use(authMiddleware())
  rG.GET("/resources/:id", fixedGetResource)
  r.Run(":8080")
}
```

Fixed pattern:
```go
import (
  "net/http"
  "strconv"

  "github.com/gin-gonic/gin"
)

type Resource struct { ID int64; OwnerID int64; Data string }

var resources = []Resource{
  {ID: 1, OwnerID: 10, Data: `secretA`},
  {ID: 2, OwnerID: 42, Data: `secretB`},
}

func findResourceByID(id int64) (Resource, bool) {
  for _, r := range resources {
    if r.ID == id { return r, true }
  }
  return Resource{}, false
}

func vulnerableGetResource(c *gin.Context) {
  idStr := c.Param("id")
  id, _ := strconv.ParseInt(idStr, 10, 64)
  if r, ok := findResourceByID(id); ok {
    c.JSON(http.StatusOK, r)
    return
  }
  c.Status(http.StatusNotFound)
}

func authMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) { c.Set("userID", int64(42)); c.Next() }
}

func fixedGetResource(c *gin.Context) {
  uidVal, ok := c.Get("userID")
  if !ok { c.Status(http.StatusUnauthorized); return }
  userID := uidVal.(int64)

  idStr := c.Param("id")
  id, _ := strconv.ParseInt(idStr, 10, 64)
  r, found := findResourceByID(id)
  if !found { c.Status(http.StatusNotFound); return }
  if r.OwnerID != userID { c.Status(http.StatusForbidden); return }
  c.JSON(http.StatusOK, r)
}

func main() {
  r := gin.Default()
  r.GET("/vulnerable/resources/:id", vulnerableGetResource)
  rG := r.Group("/")
  rG.Use(authMiddleware())
  rG.GET("/resources/:id", fixedGetResource)
  r.Run(":8080")
}
```

CVE References

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