Broken Object Level Authorization

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

[Fixed month year] Updated CVE-2026-40252

Overview

CVE-2026-40252 describes a broken object-level authorization (also CWE-284, CWE-639) in FastGPT prior to 4.14.10.4 where an authenticated user could access and execute applications belonging to other teams by supplying a foreign appId. Although the API validates the team token, it fails to verify that the requested application actually belongs to the authenticated team, enabling cross-tenant data exposure and unauthorized execution of private AI workflows. This is a classic BOLA defect: authorization is performed at a coarse level (token validation) but not at the resource level (app ownership). In Go with Gin, the vulnerability manifests when routes accept an appId path parameter and return resource data or actions without confirming ownership. The fix, as noted in the CVE, is to enforce resource ownership in each access path and couple identity (team) from the token with per-resource checks, ensuring that app.TeamID == user.TeamID before returning data or triggering actions. This guide demonstrates the impact, how an attacker could exploit it, and a concrete Go (Gin) remediation with side-by-side vulnerable and fixed implementations.

Affected Versions

Prior to 4.14.10.4

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type App struct {
  ID     string
  TeamID string
  Name   string
}

type User struct {
  ID     string
  TeamID string
}

var apps = map[string]App{
  "app-1": App{ID: "app-1", TeamID: "team-a", Name: "Alice App"},
  "app-2": App{ID: "app-2", TeamID: "team-b", Name: "Bob App"},
}

var userStore = map[string]User{
  "token-a": {ID: "u1", TeamID: "team-a"},
  "token-b": {ID: "u2", TeamID: "team-b"},
}

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

  // Mock authentication middleware: extracts user from a Bearer token and stores in context
  r.Use(func(c *gin.Context) {
    auth := c.GetHeader("Authorization")
    token := ""
    if len(auth) > 7 && auth[:7] == "Bearer " {
      token = auth[7:]
    }
    if u, ok := userStore[token]; ok {
      c.Set("user", u)
    }
    c.Next()
  })

  // Vulnerable endpoint: validates token but does NOT verify resource ownership
  r.GET("/vuln/apps/:appId/workflows", func(c *gin.Context) {
    appId := c.Param("appId")
    if a, ok := apps[appId]; ok {
      // No ownership check; returns data for any appId to any authenticated user
      c.JSON(http.StatusOK, gin.H{
        "workflows": []string{"build", "train", "evaluate"},
        "app":       a.Name,
      })
      return
    }
    c.JSON(http.StatusNotFound, gin.H{ "error": "app not found" })
  })

  // Fixed endpoint: enforce object-level authorization by checking ownership
  r.GET("/fix/apps/:appId/workflows", func(c *gin.Context) {
    appId := c.Param("appId")
    if a, ok := apps[appId]; ok {
      val, exists := c.Get("user")
      if !exists {
        c.JSON(http.StatusUnauthorized, gin.H{ "error": "unauthorized" })
        return
      }
      u := val.(User)
      if a.TeamID != u.TeamID {
        c.JSON(http.StatusForbidden, gin.H{ "error": "forbidden: app does not belong to your team" })
        return
      }
      c.JSON(http.StatusOK, gin.H{
        "workflows": []string{"build", "train", "evaluate"},
        "app":       a.Name,
      })
      return
    }
    c.JSON(http.StatusNotFound, gin.H{ "error": "app not found" })
  })

  r.Run(":8080")
}

CVE References

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