Broken Object Property Level Authorization

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

[Updated March 2026] Updated CVE-2026-33761

Overview

The WWBN AVideo CVE-2026-33761 case demonstrates a classic Broken Object Property Level Authorization flaw: endpoints handling scheduled tasks or related resources exposed sensitive data without proper authentication or authorization. In that PHP-based project, several list.json.php endpoints in the Scheduler plugin were unsecured while other endpoints required admin checks, enabling unauthenticated access to all scheduled tasks, internal callback URLs and mappings. This led to information disclosure (CWE-200) and flawed access control (CWE-862). The patch referenced (commit 83390ab1fa8dca2de3f8fa76116a126428405431) fixed the issue, but the underlying pattern remains a risk for Go (Gin) services that forget to enforce per-object authorization. In Go with Gin, this vulnerability manifests when a handler returns an object (by ID) without verifying that the current user owns it or has admin rights, allowing attackers to enumerate or retrieve data they should not see. This guide references CVE-2026-33761 to illustrate the risk and translates the remediation into concrete Go (Gin) code patterns and fixes, aligning to CWE-200 and CWE-862 concerns.

Affected Versions

WWBN AVideo <= 26.0; patch commit 83390ab1fa8dca2de3f8fa76116a126428405431; CVE-2026-33761; CWE-200, CWE-862

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type User struct {
  ID      int
  IsAdmin bool
}

type Resource struct {
  ID      string
  OwnerID int
  Data    string
}

var resources = map[string]Resource{
  "r1": {ID: "r1", OwnerID: 1, Data: "secret1"},
  "r2": {ID: "r2", OwnerID: 2, Data: "secret2"},
}

func getResourceByID(id string) (Resource, bool) {
  res, ok := resources[id]
  return res, ok
}

func main() {
  r := gin.Default()
  r.Use(mockAuthMiddleware())
  r.GET("/vuln/resource/:id", vulnerableGetResource)
  r.GET("/secure/resource/:id", secureGetResource)
  _ = r.Run(":8080")
}

// mockAuthMiddleware attaches a user to the context for demonstration purposes.
func mockAuthMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    // In production, extract user from JWT/session
    // Here we simulate two users: 1 (regular) and 99 (admin)
    userID := 1
    if v := c.GetHeader("X-User-Id"); v != "" {
      if n, err := strconv.Atoi(v); err == nil {
        userID = n
      }
    }
    isAdmin := (userID == 99)
    c.Set("user", &User{ID: userID, IsAdmin: isAdmin})
    c.Next()
  }
}

func getUserFromContext(c *gin.Context) *User {
  if v, exists := c.Get("user"); exists {
    if u, ok := v.(*User); ok {
      return u
    }
  }
  return &User{ID: 0, IsAdmin: false}
}

// Vulnerable pattern: returns resource without authorization check
func vulnerableGetResource(c *gin.Context) {
  id := c.Param("id")
  res, ok := getResourceByID(id)
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
    return
  }
  c.JSON(http.StatusOK, res)
}

// Fixed pattern: enforces per-object authorization (owner or admin)
func secureGetResource(c *gin.Context) {
  id := c.Param("id")
  res, ok := getResourceByID(id)
  if !ok {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
    return
  }
  user := getUserFromContext(c)
  if res.OwnerID != user.ID && !user.IsAdmin {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }
  c.JSON(http.StatusOK, res)
}

CVE References

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