Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [GHSA-j724-5c6c-68g5]

[Updated month year] Updated GHSA-j724-5c6c-68g5

Overview

Broken Object Property Level Authorization vulnerabilities allow attackers to manipulate or access object properties they should not be permitted to touch. In real-world Go (Gin) APIs, this can lead to unauthorized reading or modification of resource properties such as owner identifiers, status flags, or sensitive metadata, potentially resulting in data leakage, privacy violations, or privilege escalation. When apps rely on client-supplied patch data or expose endpoints that update arbitrary fields without strict per-property checks, attackers can spoof ownership or alter critical fields even if they cannot create new resources. This class of flaw is particularly dangerous in microservice or multi-tenant Gin-based services where proper scoping and explicit field-level permissions are essential. Correlated risks include audit gaps, inconsistent access control across endpoints, and difficulty tracing malicious modifications in production logs.

Code Fix Example

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

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

type Resource struct {
  ID          int64
  OwnerID     int64
  Name        string
  Description string
}

var resources = map[int64]Resource{1: {ID: 1, OwnerID: 1, Name: "Sample", Description: "A sample resource"}}

func main() {
  r := gin.Default()
  // Mock authentication: all requests treated as user 1
  r.Use(func(c *gin.Context) { c.Set("userID", int64(1)); c.Next() })
  r.PATCH("/resources/:id/vuln", patchResourceVulnerable)
  r.PATCH("/resources/:id/fix", patchResourceFixed)
  r.Run(":8080")
}

func patchResourceVulnerable(c *gin.Context) {
  userID := c.GetInt64("userID")
  id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
  var patch map[string]interface{}
  if err := c.BindJSON(&patch); err != nil { c.Status(http.StatusBadRequest); return }
  res, ok := resources[id]
  if !ok { c.Status(http.StatusNotFound); return }

  // Vulnerable: blindly apply any field provided by client, including owner_id
  if v, ok := patch["owner_id"]; ok {
    if f, ok2 := v.(float64); ok2 { res.OwnerID = int64(f) }
  }
  if v, ok := patch["name"]; ok {
    if s, ok2 := v.(string); ok2 { res.Name = s }
  }
  if v, ok := patch["description"]; ok {
    if s, ok2 := v.(string); ok2 { res.Description = s }
  }
  resources[id] = res
  c.JSON(http.StatusOK, res)
}

type ResourceUpdate struct {
  Name        *string
  Description *string
}

func patchResourceFixed(c *gin.Context) {
  userID := c.GetInt64("userID")
  id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
  var patch ResourceUpdate
  if err := c.BindJSON(&patch); err != nil { c.Status(http.StatusBadRequest); return }
  res, ok := resources[id]
  if !ok { c.Status(http.StatusNotFound); return }
  // Enforce ownership before updates
  if res.OwnerID != userID {
    c.Status(http.StatusForbidden)
    return
  }
  // Apply only explicitly allowed fields; never trust ownership or other protected fields from input
  if patch.Name != nil { res.Name = *patch.Name }
  if patch.Description != nil { res.Description = *patch.Description }
  resources[id] = res
  c.JSON(http.StatusOK, res)
}

CVE References

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