Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-966j-vmvw-g2g9]

[Updated 2026-04] Updated GHSA-966j-vmvw-g2g9

Overview

Broken Object Property Level Authorization (BOPLA) occurs when an API returns an object solely because an ID is supplied, without validating that the requester is allowed to access that specific object. In real-world Go services built with Gin, this often enables data leakage across users or tenants when endpoints fetch resources by ID from the path (for example, /resources/:id) and return the object without ownership checks. Because authentication may exist but authorization is incomplete, attackers can enumerate IDs and retrieve others' data. Note: no CVE IDs are provided for this guide. Under Gin, this vulnerability manifests as handlers that load resources by ID and return fields directly, bypassing object-level checks. The root cause is separating authentication from authorization without adding ownership-scoped access controls at the data layer or service boundary. Even with JWT validation, missing a check against the resource's owner means any authenticated user can view any resource they can guess by ID, which is a serious privacy and regulatory risk. Remediation patterns include: enforcing ownership at the data access layer, performing explicit OLA checks in handlers or services before returning data, and avoiding returning sensitive fields when ownership is uncertain. Comprehensive tests for ID enumeration and cross-user access should be added, and endpoint responses should reflect precise authorization results (403 instead of 200).

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
  `net/http`
  `github.com/gin-gonic/gin`
)

type Resource struct {
  ID    string
  Owner string
  Data  string
}

var resources = []Resource{
  {ID: `1`, Owner: `u1`, Data: `TopSecretA`},
  {ID: `2`, Owner: `u2`, Data: `TopSecretB`},
}

func main() {
  r := gin.Default()
  r.Use(authMiddleware())

  r.GET(`/vuln/resources/:id`, vulnerableHandler)
  r.GET(`/fixed/resources/:id`, fixedHandler)

  r.Run(`:8080`)
}

func authMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    user := c.GetHeader(`X-User-Id`)
    if user == `` {
      user = `u1`
    }
    c.Set(`userID`, user)
    c.Next()
  }
}

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

func vulnerableHandler(c *gin.Context) {
  id := c.Param(`id`)
  res, ok := findResourceByID(id)
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{`error`: `not found`})
    return
  }
  c.JSON(http.StatusOK, gin.H{`id`: res.ID, `owner`: res.Owner, `data`: res.Data})
}

func fixedHandler(c *gin.Context) {
  userID := c.GetString(`userID`)
  id := c.Param(`id`)
  res, ok := findResourceByID(id)
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{`error`: `not found`})
    return
  }
  if res.Owner != userID {
    c.JSON(http.StatusForbidden, gin.H{`error`: `forbidden`})
    return
  }
  c.JSON(http.StatusOK, gin.H{`id`: res.ID, `owner`: res.Owner, `data`: res.Data})
}

CVE References

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