Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-px7x-gq96-rmp5]

[Updated Mar 2026] Updated GHSA-px7x-gq96-rmp5

Overview

Broken Object Property Level Authorization vulnerabilities let attackers read or modify properties on a resource that should be restricted, even when the object itself is accessible. In practice, this means that a GET or PATCH on a user, order, or account may return or expose fields such as balances, personal notes, or internal identifiers that the user should not see. No CVEs are provided for this guide, but the described pattern reflects well-known risks when property-level access is not enforced. When exploited, attackers can gain access to sensitive data, which may lead to financial loss, privacy violations, or regulatory exposure. In Go applications using Gin, endpoints commonly load an object by ID from the path and respond with the whole struct. If the authorization check only verifies object ownership and does not filter response properties, sensitive fields can be leaked to unauthorized clients. This is especially risky in multi-tenant apps or services handling payments, PII, or internal notes. The breadth of data returned by a single endpoint can inadvertently widen an attacker’s access beyond what is permissible. This guide demonstrates the vulnerability pattern and a remediation approach. The vulnerable example returns a full User struct; the fixed example uses a DTO with only allowed fields and enforces property-level checks based on the requester’s identity or role. By separating public response models from internal representations, you minimize the risk of inadvertently exporting restricted properties from your API. For robust defense, implement strict data contracts, include property-level access checks, and test thoroughly. Consider separate handlers or DTOs per view, and verify both object- and property-level authorization in automated tests.

Code Fix Example

Go (Gin) API Security Remediation
Vulnerable pattern:
package main

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

type User struct {
  ID           int
  Username     string
  Email        string
  Balance      float64
  SecretNote   string
}

type PublicUser struct {
  ID       int
  Username string
  Email    string
}

func main() {
  r := gin.Default()
  r.GET("/vuln/users/:id", GetUserVulnerable)
  r.GET("/fix/users/:id", GetUserFixed)
  r.Run(":8080")
}

func getUserFromDB(id int) *User {
  return &User{ID: id, Username: "alice", Email: "[email protected]", Balance: 123.45, SecretNote: "internal"}
}

type Requester struct { ID int; IsAdmin bool }

func getRequester(c *gin.Context) *Requester {
  if c.Query("admin") == "1" {
    return &Requester{ID: 999, IsAdmin: true}
  }
  return &Requester{ID: 1, IsAdmin: false}
}

func GetUserVulnerable(c *gin.Context) {
  idStr := c.Param("id")
  id, err := strconv.Atoi(idStr)
  if err != nil {
    c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"})
    return
  }
  user := getUserFromDB(id)
  c.JSON(http.StatusOK, user) // vulnerable: returns all fields including Balance and SecretNote
}

func GetUserFixed(c *gin.Context) {
  idStr := c.Param("id")
  id, err := strconv.Atoi(idStr)
  if err != nil {
    c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"})
    return
  }
  user := getUserFromDB(id)
  req := getRequester(c)
  if req.ID != user.ID && !req.IsAdmin {
    c.JSON(http.StatusForbidden, map[string]string{"error": "forbidden"})
    return
  }
  safe := PublicUser{ID: user.ID, Username: user.Username, Email: user.Email}
  c.JSON(http.StatusOK, safe) // fixed: only public fields are exposed
}

CVE References

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