Broken Object Level Authorization

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

[Apr 2026] Updated CVE-2026-33318

Overview

The CVE-2026-33318 case demonstrates a Broken Object Level Authorization (BOLA) chain in a Go Gin app. In a local-first personal finance tool, three weaknesses converge to allow an authenticated attacker to become an administrator after a migration from password-based auth to OpenID Connect. The first weakness is a missing authorization check on POST /account/change-password, which lets any authenticated session overwrite any target user's password hash. The second weakness is the presence of an inactive password row persisting after migration, which can become a target. The third weakness is a client-controlled loginMethod that can bypass the server's active authentication configuration. Taken together, these flaws enable an attacker to set a known password for an anonymous admin account created during migration and authenticate as that account if the other preconditions exist. The root cause is the lack of authorization validation on /change-password; the other two weaknesses are preconditions that enable exploitation. The CVE notes that the fix landed in version 26.4.0. This guide references CVE-2026-33318 and CWE-284 and CWE-862 to help developers remediate similar patterns in Go (Gin).

Affected Versions

Before 26.4.0

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
  "net/http"
  "github.com/gin-gonic/gin"
  "golang.org/x/crypto/bcrypt"
)

type User struct {
  ID           string
  PasswordHash string
  Role         string
}

var users = map[string]User{
  "1":     {ID: "1", PasswordHash: "", Role: "USER"},
  "admin": {ID: "admin", PasswordHash: "", Role: "ADMIN"},
}

func main() {
  // simple server setup with both endpoints for demonstration
  r := gin.Default()
  r.POST("/account/change-password", ChangePasswordVuln)     // vulnerable pattern (for reference)
  r.POST("/account/change-password-secure", ChangePasswordFixed) // secure pattern
  r.Run()
}

// Vulnerable: no authorization check on target user; any authenticated caller can rewrite someone else's hash
func ChangePasswordVuln(c *gin.Context) {
  var req struct {
    TargetUserID string `json:"target_user_id"`
    NewPassword  string `json:"new_password"`
  }
  if err := c.BindJSON(&req); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
    return
  }
  // Insecure: no authorization check or ownership validation
  hash, _ := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
  if user, ok := users[req.TargetUserID]; ok {
    user.PasswordHash = string(hash)
    users[req.TargetUserID] = user
    c.JSON(http.StatusOK, gin.H{"status": "password updated (vulnerable)"})
    return
  }
  c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
}

// Fixed: enforce authorization checks and server-dictated login behavior
func ChangePasswordFixed(c *gin.Context) {
  var req struct {
    TargetUserID string `json:"target_user_id"`
    NewPassword  string `json:"new_password"`
  }
  if err := c.BindJSON(&req); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
    return
  }
  // In a real app, replace with proper auth middleware. Here we use a header to illustrate.
  currentUserID := c.GetHeader("X-User-ID")
  current, ok := users[currentUserID]
  if !ok {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
    return
  }
  // Authorization: allow password change if the caller is the target user or has ADMIN role
  if current.ID != req.TargetUserID && current.Role != "ADMIN" {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }
  hash, _ := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
  if target, ok := users[req.TargetUserID]; ok {
    target.PasswordHash = string(hash)
    users[req.TargetUserID] = target
    c.JSON(http.StatusOK, gin.H{"status": "password updated"})
    return
  }
  c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
}

CVE References

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