Broken Object Property Level Authorization

Broken Object Property Level Authorization and Go (Gin) [GHSA-xr49-f4rh-qcjf]

[Updated May 2026] Updated GHSA-xr49-f4rh-qcjf

Overview

Broken Object Property Level Authorization vulnerabilities allow attackers to access or manipulate data they should not see by simply altering identifiers in requests. In multi-tenant apps or services that store user-owned resources, this can lead to data leakage, privacy violations, or unauthorized edits and deletions. The impact scales with sensitive data like personally identifiable information, financial records, or protected content. In Go with the Gin framework, these flaws often appear on endpoints that fetch, update, or delete a resource by ID, such as GET /resources/:id or PATCH /resources/:id, without tying the operation to the authenticated user's identity. An attacker can arbitrarily enumerate IDs and access resources belonging to others, or compromise data integrity by modifying someone else's resource if proper ownership checks are absent. This class manifests when the server loads the requested object and then uses the caller's identity to decide if the action is allowed, but the authorization check is not consistently enforced at the data access boundary or the check is ineffective. Often the fix is to filter by owner_id in the DB query and/or verify ownership in middleware or a dedicated authorization layer before returning or mutating the object. Remediation strategy includes establishing a robust user-context mechanism, performing ownership checks for every object-level operation, and failing closed on any mismatch. Use parameterized queries that include owner constraints, centralize authorization logic, and accompany changes with tests and security reviews.

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
    "net/http"
    "strconv"

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

type Resource struct {
    ID     int
    UserID int
    Data   string
}

var resourceStore = map[int]Resource{
    1: {ID: 1, UserID: 100, Data: `Resource 1`},
    2: {ID: 2, UserID: 200, Data: `Resource 2`},
}

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

    // Vulnerable: no ownership check
    r.GET(`/vuln/resources/:id`, func(c *gin.Context) {
        id, err := strconv.Atoi(c.Param(`id`))
        if err != nil {
            c.Status(http.StatusBadRequest)
            return
        }
        res, ok := resourceStore[id]
        if !ok {
            c.Status(http.StatusNotFound)
            return
        }
        c.JSON(http.StatusOK, res)
    })

    // Fixed: ownership check
    r.GET(`/secure/resources/:id`, func(c *gin.Context) {
        id, err := strconv.Atoi(c.Param(`id`))
        if err != nil {
            c.Status(http.StatusBadRequest)
            return
        }
        res, ok := resourceStore[id]
        if !ok {
            c.Status(http.StatusNotFound)
            return
        }
        userID := getAuthenticatedUserID(c)
        if res.UserID != userID {
            c.Status(http.StatusForbidden)
            return
        }
        c.JSON(http.StatusOK, res)
    })

    r.Run(":8080")
}

func getAuthenticatedUserID(c *gin.Context) int {
    idStr := c.GetHeader(`X-User-ID`)
    id, _ := strconv.Atoi(idStr)
    return id
}

CVE References

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