Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go (Gin) [CVE-2026-33745]

[Updated Mar 2026] Updated CVE-2026-33745

Overview

Broken Object Property Level Authorization (BOLA) vulnerabilities arise when an API exposes parts of a resource (properties) without validating the caller's rights to access each property. This can lead to attackers viewing or mutating fields they should not have access to. A real-world analogue is CVE-2026-33745, where cpp-httplib forwarded stored Basic Auth, Bearer Token, and Digest credentials to arbitrary hosts during cross-origin redirects, enabling credential leakage if a malicious server redirected the client to an attacker-controlled host (CWE-200). While that CVE targets a C++ library, the underlying risk-trusting input or upstream behavior and leaking sensitive state across boundaries-maps to how per-property authorization can fail in Go (Gin). In a Go/Gin context, lacking granular checks on which fields of a resource a user may read or modify can expose secrets, ownership metadata, or other sensitive properties to unauthorized users. This guide ties the concept to Go-based services and shows concrete remediation.

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
    "net/http"
    "strconv"

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

type Item struct {
    ID       int64  `json:"id"`
    Name     string `json:"name"`
    OwnerID  int64  `json:"owner_id"`
    Secret   string `json:"secret"` // sensitive property that should be hidden from non-owners
}

type ItemPublic struct {
    ID   int64  `json:"id"`
    Name string `json:"name"`
}

type User struct {
    ID int64
}

var items = map[int64]Item{
    1: {ID: 1, Name: "Report", OwnerID: 42, Secret: "topsecret"},
    2: {ID: 2, Name: "Invoice", OwnerID: 7, Secret: "baz"},
}

func main() {
    r := gin.Default()
    // Vulnerable pattern: exposes full Item including sensitive fields
    r.GET("/vuln/items/:id", vulnerableGetItem)
    // Fixed pattern: enforces ownership and returns sanitized view
    r.GET("/fix/items/:id", fixedGetItem)
    r.Run(":8080")
}

func getUserFromHeader(c *gin.Context) User {
    switch c.GetHeader("X-User") {
    case "42":
        return User{ID: 42}
    case "7":
        return User{ID: 7}
    default:
        return User{ID: 0}
    }
}

// Vulnerable handler: returns the full resource, including sensitive fields
func vulnerableGetItem(c *gin.Context) {
    idParam := c.Param("id")
    id, err := strconv.ParseInt(idParam, 10, 64)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
        return
    }
    item, ok := items[id]
    if !ok {
        c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
        return
    }
    // No authorization check on per-property access; all fields are returned
    c.JSON(http.StatusOK, item)
}

// Fixed handler: enforces ownership and sanitizes response to expose only public fields
func fixedGetItem(c *gin.Context) {
    idParam := c.Param("id")
    id, err := strconv.ParseInt(idParam, 10, 64)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
        return
    }
    item, ok := items[id]
    if !ok {
        c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
        return
    }
    user := getUserFromHeader(c)
    // Per-object authorization: ensure the caller owns the resource
    if user.ID != item.OwnerID {
        c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
        return
    }
    // Sanitize response to exclude sensitive fields
    public := ItemPublic{ID: item.ID, Name: item.Name}
    c.JSON(http.StatusOK, public)
}

CVE References

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