Overview
Broken Object Property Level Authorization (a form of access control failure) can allow a low-privilege attacker to access or control resources that should be restricted. In CloudStack deployments that use the Proxmox extension, vulnerable patterns were seen where an instance field proxmox_vmid is user-editable and used to bind a CloudStack instance to a specific Proxmox VM. This is a CWE-200-style exposure and was reported under CVE-2026-25199. Versions 4.21.0.0 through 4.22.0.0 of the CloudStack Proxmox extension were affected, enabling cross-tenant access and full VM control for non-privileged users.
Exploitation typically involves changing the proxmox_vmid value on an instance to the ID of a Proxmox VM owned by another tenant. Because the VM IDs are predictable and the server trusts this value, the attacker can issue actions (start/stop/destroy) against that VM without verifying tenant ownership.
In Go (Gin) services that manage such third-party integrations, a vulnerable pattern looks like reading proxmox_vmid from a request payload and passing it directly to the Proxmox API without validating ownership against the authenticated user. The fix is to enforce object-level authorization: ensure the proxmox_vmid is mapped to the current user\'s VM and reject requests for others.
To remediate, either upgrade to CloudStack 4.22.0.1 or apply a server-side mapping validation approach. The guidance section provides a concrete code example showing the vulnerable and fixed patterns in Go (Gin).
Affected Versions
CloudStack Proxmox extension 4.21.0.0 through 4.22.0.0; fixed in 4.22.0.1
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type ProxmoxRequest struct {
ProxmoxVMID string `json:"proxmox_vmid"`
}
type VM struct {
ProxmoxVMID string
OwnerID int64
Name string
}
var vmStore = map[string]VM{
"101": {ProxmoxVMID: "101", OwnerID: 1, Name: "TenantA-VM"},
"202": {ProxmoxVMID: "202", OwnerID: 2, Name: "TenantB-VM"},
}
func main() {
r := gin.Default()
// Mock middleware to set current user
r.Use(func(c *gin.Context) {
c.Set("user_id", int64(1)) // In real code, extract from auth token/session
c.Next()
})
r.POST("/vuln/assign", vulnerableAssign)
r.POST("/fix/assign", fixedAssign)
_ = r.Run(":8080")
}
func currentUserID(c *gin.Context) int64 {
v, ok := c.Get("user_id")
if !ok {
return 0
}
return v.(int64)
}
// Vulnerable: uses proxmox_vmid directly without ownership check
func vulnerableAssign(c *gin.Context) {
var req ProxmoxRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
// No ownership validation: vulnerability
c.JSON(http.StatusOK, gin.H{"status": "action performed on proxmox_vmid", "vmid": req.ProxmoxVMID})
}
// Fixed: validates ownership by mapping proxmox_vmid to VM and checking OwnerID
func fixedAssign(c *gin.Context) {
var req ProxmoxRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
vm, ok := vmStore[req.ProxmoxVMID]
if !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "vm not found"})
return
}
if vm.OwnerID != currentUserID(c) {
c.JSON(http.StatusForbidden, gin.H{"error": "not authorized to access this VM"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "action performed on proxmox_vmid", "vmid": req.ProxmoxVMID, "owner": vm.OwnerID})
}