Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) [Apr 2026] [GHSA-w942-j9r6-hr6r]

[Updated Apr 2026] Updated GHSA-w942-j9r6-hr6r

Overview

Broken Object Level Authorization (BOLA) occurs when a system fails to verify that the authenticated user is authorized to access a specific object. In practice, this lets attackers read, modify, or delete other users' data by guessing or manipulating object IDs in requests. In Go applications using Gin, a tempting pattern is to trust the object ID in the URL and rely on authentication alone, assuming the user can't access other IDs.\n\nReal-world impact includes leakage of sensitive records, financial data, or personal information. An attacker may enumerate resource IDs and extract data or perform unauthorized actions.\n\nManifestation in Gin: If handlers perform a query by id only and then return the result, or construct queries like find where id = ? without checking ownership, BOLA occurs. Typical endpoints include /resources/:id, /orders/:id. Attackers can craft requests with valid IDs to access others' resources.\n\nRemediation approach: Enforce per-object authorization checks in every handler by tying the object to the current user. Always include owner or access scope in data queries (e.g., where id = ? and owner_id = ?). Consider middleware that loads the current user and use repository/service layer checks. Add unit/integration tests that cover positive and negative cases for many objects.

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
  "log"
  "net/http"

  "github.com/gin-gonic/gin"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

type User struct {
  ID   uint   `gorm:"primaryKey"`
  Name string
}

type Resource struct {
  ID      uint   `gorm:"primaryKey"`
  OwnerID uint
  Data    string
}

var db *gorm.DB

func main() {
  var err error
  db, err = gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  if err != nil { log.Fatal(err) }

  db.AutoMigrate(&User{}, &Resource{})

  // Seed
  db.Create(&User{ID:1, Name:"Alice"})
  db.Create(&User{ID:2, Name:"Bob"})
  db.Create(&Resource{ID:1, OwnerID:1, Data:"TopSecret1"})
  db.Create(&Resource{ID:2, OwnerID:2, Data:"TopSecret2"})

  r := gin.Default()
  // simple auth middleware for demo purposes
  r.Use(func(c *gin.Context) {
    id := c.GetHeader("X-User-ID")
    if id == "" {
      c.AbortWithStatus(http.StatusUnauthorized)
      return
    }
    var u User
    if id == "1" {
      u = User{ID:1, Name:"Alice"}
    } else if id == "2" {
      u = User{ID:2, Name:"Bob"}
    } else {
      c.AbortWithStatus(http.StatusUnauthorized)
      return
    }
    c.Set("user", u)
    c.Next()
  })

  r.GET("/v1/resources/:id", vulnerableHandler)
  r.GET("/v1/resources-secure/:id", secureHandler)

  log.Fatal(r.Run(":8080"))
}

func vulnerableHandler(c *gin.Context) {
  id := c.Param("id")
  var res Resource
  if err := db.First(&res, "id = ?", id).Error; err != nil {
     c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
     return
  }
  // Vulnerable: no ownership check
  c.JSON(http.StatusOK, res)
}

func secureHandler(c *gin.Context) {
  id := c.Param("id")
  userVal, exists := c.Get("user")
  if !exists {
     c.AbortWithStatus(http.StatusUnauthorized)
     return
  }
  user := userVal.(User)
  var res Resource
  // Enforce ownership in query
  if err := db.First(&res, "id = ? AND owner_id = ?", id, user.ID).Error; err != nil {
     c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
     return
  }
  c.JSON(http.StatusOK, res)
}

CVE References

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