Broken Object Property Level Authorization

Broken Object Property Level Authorization in Go-Gin [CVE-2026-34839]

[Updated Apr 2026] Updated CVE-2026-34839

Overview

Broken Object Property Level Authorization (BOLLA) vulnerabilities occur when an API returns object data or its properties without verifying that the requesting user is allowed access. The CVE-2026-34839 case in Glances demonstrates how a REST API exposed via '/api/4/*' with no authentication and a permissive CORS policy (Access-Control-Allow-Origin: *) can enable cross-origin data exfiltration. While Glances is not written in Go, the underlying risk - information disclosure when authorization checks are skipped and CORS is misconfigured - is directly applicable to Go (Gin) APIs. In a Go/Gin context, attackers can access sensitive fields of a resource (for example, secret or owner-only data) simply by requesting a resource ID that they do not own, especially if you return full structs or use unfiltered DTOs. This mirrors CWE-200 (Information Exposure) and CWE-306 (Missing Authentication for Critical Data), and can be amplified by misconfigured CORS (CWE-942).

Affected Versions

Glances: prior to 4.5.4 (vulnerable); fixed in 4.5.4.

Code Fix Example

Go (Gin) API Security Remediation
Vulnerable pattern (Go + Gin):
package main

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

type Resource struct {
  ID      int64  `json:"id"`
  OwnerID int64  `json:"owner_id"`
  Data    string `json:"data"`
  Secret  string `json:"secret"`
}

var store = map[string]Resource{
  "1": {ID: 1, OwnerID: 1001, Data: "public data", Secret: "topsecret"},
  "2": {ID: 2, OwnerID: 1002, Data: "more data", Secret: "verysecret"},
}

func fetchResource(id string) Resource {
  if r, ok := store[id]; ok {
    return r
  }
  return Resource{ID: 0, OwnerID: 0, Data: "", Secret: ""}
}

func vulnResource(c *gin.Context) {
  id := c.Param("id")
  res := fetchResource(id) // no auth check, returns entire object including Secret
  c.JSON(http.StatusOK, res)
}

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

  // Vulnerable CORS: allows any origin
  vuln := r.Group("/vuln")
  vuln.Use(func(c *gin.Context) {
    c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
    if c.Request.Method == http.MethodOptions {
      c.AbortWithStatus(http.StatusNoContent)
      return
    }
    c.Next()
  })
  vuln.GET("/resource/:id", vulnResource)

  // Fixed path: proper auth, per-object authorization, and restricted CORS
  secure := r.Group("/secure")
  secure.Use(func(c *gin.Context) {
    // Simple example auth (replace with real JWT/OAuth in prod)
    userID := c.GetHeader("Authorization")
    if userID == "" {
      c.AbortWithStatus(http.StatusUnauthorized)
      return
    }
    c.Set("userID", userID)
    c.Next()
  })
  secure.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://trust.example.com"},
    AllowMethods:     []string{"GET"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    AllowCredentials: true,
  }))
  secure.GET("/resource/:id", func(c *gin.Context) {
    // Extract user from header (example only)
    user := c.GetHeader("Authorization")
    // In a real app, decode token and extract user id

    id := c.Param("id")
    res := fetchResource(id)

    // Ownership check (per-object authorization)
    if user != "" && res.OwnerID != 0 {
      // Simulated userID from header matches OwnerID (for demo)
      // In production: compare against parsed token userID
      // if int64(parsedUserID) != res.OwnerID { c.AbortWithStatus(http.StatusForbidden) ; return }
      _ = user
      // Here we enforce ownership: only allow if the requester owns the resource
      // For demonstration, assume ownership verified when header equals OwnerID string
      if user != string(rune(res.OwnerID)) {
        c.AbortWithStatus(http.StatusForbidden)
        return
      }
    }

    // Safe response: do not leak secrets
    safe := gin.H{
      "id":   res.ID,
      "data": res.Data,
    }
    c.JSON(http.StatusOK, safe)
  })

  r.Run(":8080")
}

Vulnerable pattern (side-by-side):
package main

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

type Resource struct { ID int64 `json:"id"`; OwnerID int64 `json:"owner_id"`; Data string `json:"data"`; Secret string `json:"secret"` }

var store = map[string]Resource{
  "1": {ID: 1, OwnerID: 1001, Data: "public data", Secret: "topsecret"},
}

func vulnResource(c *gin.Context) { res := store[c.Param("id")]; c.JSON(http.StatusOK, res) }

func main() {
  r := gin.Default()
  r.GET("/vuln/resource/:id", vulnResource)
  r.Run(":8081")
}

CVE References

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