Overview
Broken Object Property Level Authorization (BOPLA) flaws occur when apps trust client-supplied object properties (such as owner_id) to authorize access or updates. In Go using Gin, handlers that bind JSON payloads and then apply authorization logic using these properties can be misled if the server ends up updating the property values rather than validating them. This enables attackers to manipulate ownership or permissions and access resources they should not see.
In practice, endpoints that accept resource updates by ID but allow the payload to set owner_id or other sensitive fields create a risk. If the update uses the client-provided owner_id to decide who owns the resource, or if ownership is stored as a field updated by the same request, an attacker can reassign resources, view others' data, or escalate privileges.
Remediation focuses on server-side enforcement of object-level authorization: do not trust client-supplied ownership fields, fetch and verify the current resource ownership against the authenticated user, and restrict updates to allowed fields. Consider RBAC/ABAC plus explicit ownership checks and immutable ownership columns unless transfer is strictly allowed and audited.
Code Fix Example
Go (Gin) API Security Remediation
// Vulnerable pattern in Gin (Go)
package main
import (
"database/sql"
"net/http"
"github.com/gin-gonic/gin"
)
type UpdateResourceRequest struct {
OwnerID int `json:"owner_id"`
Title string `json:"title"`
}
var db *sql.DB // assume initialized elsewhere
func vulnerableUpdateResource(c *gin.Context) {
id := c.Param("id")
var req UpdateResourceRequest
if err := c.BindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
// Vulnerable: trust client-provided ownership and update arbitrary fields
_, err := db.Exec("UPDATE resources SET title = ?, owner_id = ? WHERE id = ?", req.Title, req.OwnerID, id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "updated"})
}
// Fixed: enforce server-side ownership and prevent changing ownership
func secureUpdateResource(c *gin.Context) {
id := c.Param("id")
var req struct { Title string `json:"title"` }
if err := c.BindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
userIDVal, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
userID, _ := userIDVal.(int)
// Retrieve current ownership to enforce object-level authorization
var ownerID int
if err := db.QueryRow("SELECT owner_id FROM resources WHERE id = ?", id).Scan(&ownerID); err != nil {
if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "resource not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to verify ownership"})
return
}
if ownerID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
return
}
// Safe update: do not allow changing ownership
_, err := db.Exec("UPDATE resources SET title = ? WHERE id = ?", req.Title, id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "updated"})
}