Broken Object Level Authorization

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

[Fixed March 2026] Updated CVE-2026-32245

Overview

Broken Object Level Authorization (BOLOA) vulnerabilities occur when an API trusts the client to decide which resources to access rather than enforcing access controls on the server side. In CVE-2026-32245, the Tinyauth OIDC token endpoint failed to verify that the client exchanging an authorization code was the same client to which the code had been issued. An attacker controlling a malicious OIDC client could redeem another user's authorization code using their own client credentials to obtain tokens for that user, effectively bypassing the intended authorization boundary. This directly violates RFC 6749 Section 4.1.3 and enables token theft across apps, granting the attacker access to user data and actions intended for the legitimate client. The CVE is categorized under CWE-863 (Incorrect Authorization) and was fixed in 5.0.3 of Tinyauth. While this CVE references a specific OIDC server, the underlying remediation principle applies to Go (Gin) APIs that implement OAuth2/OIDC token endpoints: bind codes to clients and enforce proper client authentication at token exchange to prevent cross-client token issuance.

Affected Versions

CVE-2026-32245: Tinyauth OIDC server affected prior to 5.0.3; fixed in 5.0.3. Go/Gin libraries themselves are not versioned here (N/A for the vulnerability in a Go/Gin app).

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type CodeData struct {
  Code         string
  ClientID     string
  RedirectURI  string
  UserID       string
  ClientSecret string
}

var codeStore = map[string]CodeData{
  "sample-code": {Code: "sample-code", ClientID: "client-123", RedirectURI: "http://localhost/callback", UserID: "user42", ClientSecret: "secret-abc"},
}

func main() {
  r := gin.Default()
  r.POST("/token_vulnerable", TokenEndpointVulnerable)
  r.POST("/token_fixed", TokenEndpointFixed)
  r.Run(":8080")
}

// Vulnerable: token endpoint that does not bind the authorization code to the requesting client
func TokenEndpointVulnerable(c *gin.Context) {
  code := c.PostForm("code")
  data, ok := codeStore[code]
  if !ok {
    c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_grant"})
    return
  }
  // No client binding: any client can exchange a code and get tokens for the associated user
  c.JSON(http.StatusOK, gin.H{"access_token": "atk-1", "token_type": "Bearer", "user": data.UserID})
}

// Fixed: token endpoint enforces client binding and authentication
func TokenEndpointFixed(c *gin.Context) {
  code := c.PostForm("code")
  user, pass, ok := c.Request.BasicAuth()
  if !ok {
    c.Header("WWW-Authenticate", "Basic realm=\"OAuth2 Token Endpoint\"")
    c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid_client"})
    return
  }
  data, exists := codeStore[code]
  if !exists {
    c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_grant"})
    return
  }
  // Bind the code to the requesting client
  if data.ClientID != user {
    c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_grant"})
    return
  }
  if data.ClientSecret != pass {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid_client"})
    return
  }
  // Optional: validate redirect_uri matches the code's binding
  redirectURI := c.PostForm("redirect_uri")
  if redirectURI != "" && redirectURI != data.RedirectURI {
    c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_request"})
    return
  }
  c.JSON(http.StatusOK, gin.H{"access_token": "atk-2", "token_type": "Bearer", "user": data.UserID})
}

CVE References

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