Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-g2qj-prgh-4g9r]

[Updated Apr 2026] Updated GHSA-g2qj-prgh-4g9r

Overview

Broken Object Property Level Authorization (BOPLA) vulnerabilities occur when an API allows access to a resource by simply changing an object identifier without verifying that the caller is allowed to access it. In Go using Gin, handlers that fetch resources by ID can leak data if per-object access checks are not performed. In practice, an attacker can enumerate IDs in path parameters like /users/123 and receive the corresponding resource if the server only checks for existence or relies on non-dedicated ownership metadata. This pattern is common when routes return data based on the ID without confirming ownership or privileges in the service or middleware layer. Remediation centers on enforcing authorization at the resource boundary: verify the requester’s identity, confirm ownership or admin privileges, and reject requests that fail those checks. Use a centralized function or middleware to perform per-object checks, and complement with tests that simulate ID tampering and privilege escalation.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type User struct {
  ID   string
  Name string
  Role string
}

var users = map[string]User{
  "1": {ID: "1", Name: "Alice", Role: "user"},
  "2": {ID: "2", Name: "Bob", Role: "admin"},
  "3": {ID: "3", Name: "Charlie", Role: "user"},
}

func getCurrentUser(c *gin.Context) *User {
  id := c.GetHeader("X-User-ID")
  if id == "" {
    return nil
  }
  if u, ok := users[id]; ok {
    return &u
  }
  return nil
}

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

  // Vulnerable example: returns user by ID without authorization check
  r.GET(`/users/:id`, func(c *gin.Context) {
    id := c.Param("id")
    if u, ok := users[id]; ok {
      c.JSON(http.StatusOK, u)
      return
    }
    c.JSON(http.StatusNotFound, gin.H{"error": `not found`})
  })

  // Fixed example: enforces ownership or admin role
  r.GET(`/secure/users/:id`, func(c *gin.Context) {
    id := c.Param("id")
    u, ok := users[id]
    if !ok {
      c.JSON(http.StatusNotFound, gin.H{"error": `not found`})
      return
    }
    cur := getCurrentUser(c)
    if cur == nil {
      c.JSON(http.StatusUnauthorized, gin.H{"error": `unauthorized`})
      return
    }
    if cur.ID != u.ID && cur.Role != "admin" {
      c.JSON(http.StatusForbidden, gin.H{"error": `forbidden`})
      return
    }
    c.JSON(http.StatusOK, u)
  })

  r.Run(":8080")
}

CVE References

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