Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) [Mar 2026] [CVE-2026-32758]

[Updated Mar 2026] Updated CVE-2026-32758

Overview

The CVE-2026-32758 case demonstrates a break in object-level authorization tied to path traversal in a file management context. Authenticated users with Create or Rename permissions could bypass administrator-imposed deny rules by injecting .. sequences into a destination parameter used during a PATCH-like operation. While the destination path is checked against access rules before normalization, the subsequent file operation applies path.Clean(), which resolves the traversal sequences and yields an effective path outside the intended scope. This allows writing or moving files into deny-rule-protected areas within the user’s allowed BasePathFs scope, without escaping that scope or reading restricted paths. The vulnerability is classified under CWE-22 (Path Traversal) and CWE-863 (RCE via BOLOA-related logic), and it was fixed in File Browser version 2.62.0 (CVE-2026-32758).

Affected Versions

File Browser 2.61.2 and below (CVE-2026-32758); fixed in 2.62.0 and later.

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
  "github.com/gin-gonic/gin"
  "net/http"
  "path/filepath"
  "os"
  "strings"
)

const baseDir = "./files"

type User struct {
  ID          string
  Permissions map[string]bool
}

func getUserFromContext(c *gin.Context) *User {
  // In a real app, extract from session/token; this is a minimal stub for demonstration
  return &User{ID: "u1", Permissions: map[string]bool{"create": true, "rename": true}}
}

func hasAccess(u *User, action string, path string) bool {
  // Placeholder: in real code, implement object-level checks, possibly per-path
  return u.Permissions[action]
}

// Vulnerable pattern: authorization check happens on raw input, then path.Clean() is applied only at the file op time
func vulnerablePatchHandler(c *gin.Context) {
  user := getUserFromContext(c)
  dest := c.PostForm("destPath")

  // Authorization based on raw input (vulnerable)
  if !hasAccess(user, "rename", dest) {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }

  // Later, normalization happens only for the actual operation
  finalPath := filepath.Clean(filepath.Join(baseDir, dest))
  os.MkdirAll(filepath.Dir(finalPath), 0755)
  if err := os.WriteFile(finalPath, []byte("vulnerable content"), 0644); err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    return
  }
  c.JSON(http.StatusOK, gin.H{"path": finalPath})
}

// Helper to verify path containment after normalization
func isSubPath(base, p string) (bool, error) {
  absBase, err := filepath.Abs(base)
  if err != nil {
    return false, err
  }
  absP, err := filepath.Abs(p)
  if err != nil {
    return false, err
  }
  rel, err := filepath.Rel(absBase, absP)
  if err != nil {
    return false, err
  }
  // Ensure the relative path does not go up directories
  return !strings.HasPrefix(rel, ".."), nil
}

// Fixed pattern: normalize first, then enforce containment and object-level authorization
func fixedPatchHandler(c *gin.Context) {
  user := getUserFromContext(c)
  dest := c.PostForm("destPath")

  joined := filepath.Join(baseDir, dest)
  cleaned := filepath.Clean(joined)

  within, err := isSubPath(baseDir, cleaned)
  if err != nil || !within {
    c.JSON(http.StatusForbidden, gin.H{"error": "destination outside allowed directory"})
    return
  }

  // Authorization on the final, normalized path
  if !hasAccess(user, "rename", cleaned) {
    c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
    return
  }

  os.MkdirAll(filepath.Dir(cleaned), 0755)
  if err := os.WriteFile(cleaned, []byte("fixed content"), 0644); err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    return
  }
  c.JSON(http.StatusOK, gin.H{"path": cleaned})
}

func main() {
  r := gin.Default()
  r.POST("/vuln/patch", vulnerablePatchHandler)
  r.POST("/fix/patch", fixedPatchHandler)
  r.Run(":8080")
}

CVE References

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