Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) [Month Year] [GHSA-fmh9-gpqh-g53g]

[Fixed month year] Updated GHSA-fmh9-gpqh-g53g

Overview

Broken Object Level Authorization (BOLA) in web APIs occurs when an endpoint returns an object based on an identifier without verifying that the caller is allowed to access that specific object. In production Go services using the Gin framework, such flaws often arise when endpoints construct responses by ID alone and return the object regardless of the requester's rights. Real-world impact includes attackers reading or altering data belonging to other users, escalating privileges by manipulating resource identifiers, and broadening the attack surface in multi-tenant or SaaS applications. Without proper ownership checks, an API may reveal sensitive information or allow unauthorized modifications simply by guessing or enumerating IDs. In Go with Gin, BOLA commonly manifests if a handler loads a resource by ID from the path and returns it without confirming the current user owns or has rights to that object. This guide presents a straightforward pattern to fix such endpoints by tying authorization to the authenticated user (for example, ownerID) and by filtering data both at the API boundary and the data layer. Adopting a defense-in-depth approach, you should enforce deny-by-default and apply explicit checks in each resource access path, complemented by tests that exercise unauthorized scenarios.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Resource struct {
  ID string
  OwnerID string
  Data string
}

var resources = map[string]Resource{
  "1": {ID: "1", OwnerID: "alice", Data: "secret A"},
  "2": {ID: "2", OwnerID: "bob", Data: "secret B"},
}

func fakeAuthMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    user := c.GetHeader("X-User")
    if user != "" {
      c.Set("userID", user)
    }
    c.Next()
  }
}

// Vulnerable: returns resource without ownership check
func getResourceVuln(c *gin.Context) {
  id := c.Param("id")
  res, ok := resources[id]
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
    return
  }
  c.JSON(http.StatusOK, res)
}

// Fixed: enforces ownership before returning the resource
func getResourceFixed(c *gin.Context) {
  id := c.Param("id")
  res, ok := resources[id]
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
    return
  }
  user, exists := c.Get("userID")
  if !exists || user.(string) != res.OwnerID {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }
  c.JSON(http.StatusOK, res)
}

func main() {
  r := gin.Default()
  r.Use(fakeAuthMiddleware())
  r.GET("/vuln/resources/:id", getResourceVuln)
  r.GET("/secure/resources/:id", getResourceFixed)
  r.Run(":8080")
}

CVE References

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