Overview
Broken Object Level Authorization (BOLA) occurs when an API exposes the ability to access, modify, or delete resources based solely on the fact that a request is authenticated, without validating that the resource belongs to the caller. Real-world exploitation often involves an attacker altering a numeric or opaque path parameter (such as /documents/123) to access another user's data.
In Go using the Gin framework, common manifestations include handlers that retrieve resources by ID from the URL and return them after only checking authentication, or using a generic data access function that implicitly trusts the path parameter. If the handler does not verify that resource.OwnerID matches the authenticated user (or that the user has an admin RBAC role), the attacker can read, modify, or delete data they should not reach.
Remediation patterns include enforcing explicit ownership checks in handlers or middleware, using queries that tie resource ownership to the user (for example, id plus owner_id in WHERE clause), centralizing authorization logic, and adding tests to simulate unauthorized access. Pair these with robust authentication, proper error handling, and rate limiting to reduce risk and blast radius.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type Document struct {
ID int
OwnerID int
Content string
}
type User struct {
ID int
Name string
Role string
}
var documents = map[int]Document{
1: {ID: 1, OwnerID: 100, Content: "Secret report"},
2: {ID: 2, OwnerID: 200, Content: "Public note"},
}
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// In real life, set user after authentication
c.Set("currentUser", &User{ID: 100, Name: "Alice", Role: "user"})
c.Next()
}
}
// Vulnerable example: no authorization check
func vulnerabilityGetDocument(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
doc, ok := documents[id]
if !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
// Vulnerable: returns resource regardless of ownership
c.JSON(http.StatusOK, doc)
}
func fixedGetDocument(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
doc, ok := documents[id]
if !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
userIface, exists := c.Get("currentUser")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
user := userIface.(*User)
if doc.OwnerID != user.ID && user.Role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
c.JSON(http.StatusOK, doc)
}
func main() {
r := gin.Default()
r.Use(authMiddleware())
r.GET("/vulnerable/documents/:id", vulnerabilityGetDocument)
r.GET("/fixed/documents/:id", fixedGetDocument)
r.Run(":8080")
}