Broken Object Level Authorization

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

[Updated Apr 2026] Updated CVE-2026-40865

Overview

The CVE-2026-40865 describes a Broken Object Level Authorization flaw in Horilla HRMS version 1.5.0 where an insecure direct object reference in the employee document viewer lets any authenticated user change a document ID to access other employees’ uploaded documents. This exposes sensitive HR files such as identity documents, contracts, and certificates. The vulnerability aligns with CWE-284 and CWE-639, illustrating weak access checks and authorization bypass at the object level. In Go (Gin) terms, this manifests when an API path accepts an object identifier and returns the resource without validating whether the requester owns or has permission to access it. Exploitation occurs when an attacker, already authenticated, substitutes the document identifier in the request (for example, by changing the path parameter) and retrieves a document that belongs to another user. The service merely looks up the document by its ID and returns the resource, effectively bypassing ownership checks. This is a classic object-level authorization failure: the system correctly authenticates users but fails to enforce authorization on a per-object basis, enabling data leakage across user boundaries. Remediation in Go (Gin) centers on enforcing resource ownership via context-bound user identity and scoped data access. Bind the authenticated user ID from a trusted source (e.g., JWT) into the request context, then always verify that the requested object belongs to that user (or that the user has explicit permission) before returning data. Prefer queries that filter by both id and owner_id, implement RBAC/ABAC where appropriate, and add tests to cover positive and negative cases. After refactoring, verify no similar object-level endpoints remain exposed without proper authorization. Code example below shows the vulnerable pattern and the fix side by side to help patch quickly. The vulnerable handler returns a document by ID without ownership filtering; the fixed handler checks doc.OwnerID against the authenticated user before responding.

Affected Versions

Horilla HRMS 1.5.0

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Document struct {
  ID string `json:\"id\"`
  OwnerID string `json:\"owner_id\"`
  Content string `json:\"content\"`
}

var documents = []Document{
  {ID: "doc-1", OwnerID: "alice", Content: "Alice's doc"},
  {ID: "doc-2", OwnerID: "bob", Content: "Bob's doc"},
}

func getDocumentByID(docID string) *Document {
  for i := range documents {
    if documents[i].ID == docID {
      return &documents[i]
    }
  }
  return nil
}

func main() {
  r := gin.Default()
  // Lightweight auth middleware for demonstration
  r.Use(func(c *gin.Context) {
    c.Set("userID", "alice")
    c.Next()
  })

  // Vulnerable: returns by doc ID without ownership check
  r.GET("/documents/:docID", func(c *gin.Context) {
    docID := c.Param("docID")
    if doc := getDocumentByID(docID); doc != nil {
      c.JSON(http.StatusOK, gin.H{"id": doc.ID, "owner_id": doc.OwnerID, "content": doc.Content})
      return
    }
    c.Status(http.StatusNotFound)
  })

  // Fixed: checks ownership against authenticated user
  r.GET("/secure/documents/:docID", func(c *gin.Context) {
    userID, _ := c.Get("userID")
    docID := c.Param("docID")
    if doc := getDocumentByID(docID); doc != nil {
      if doc.OwnerID != userID {
        c.Status(http.StatusForbidden)
        return
      }
      c.JSON(http.StatusOK, gin.H{"id": doc.ID, "owner_id": doc.OwnerID, "content": doc.Content})
      return
    }
    c.Status(http.StatusNotFound)
  })

  r.Run(":8080")
}

CVE References

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