flawopen.com/bola-idor/Go

● CWE-639 · Crítica
Investigación · FlawOpen

Broken Object Level Authorization (BOLA / IDOR) in Go

Prevent BOLA (CWE-639) and IDOR in Go REST APIs (Gin / Chi / GORM) by binding SQL queries directly to authenticated tenant context instead of trusting URL parameters.

💡 Explicación en Lenguaje Sencillo (ELI5)

Imagine checking into Room 204 at a hotel and receiving a valid keycard. But you discover that if you scratch out '204' on the door handle and write '205', the lock clicks open and allows you to rummage through another guest's luggage. The hotel confirmed you were a registered guest (authentication), but never checked whether your key matched Room 205 (object authorization). In BOLA/IDOR, an attacker changes an ID in an API request and accesses another user's private records.

Conceptos Clave y Términos

Authentication vs. Authorization
Authentication verifies identity. Authorization checks whether that identity owns or has permission for the specific resource.
BOLA (OWASP API #1)
Failure to validate object ownership, enabling attackers to access other tenants' data by altering request parameters.
Tenancy Query Binding
Passing claims from the request context into the SQL WHERE clause (db.Where("id = ? AND org_id = ?", id, orgID)).

Flujo de Ataque Paso a Paso

Step 1

Token Issuance

User logs into the Go API service and receives a valid JWT containing user_id and org_id claims.

Step 2

Route Invocation

User sends GET /api/v1/invoices/:id to fetch an invoice record.

Step 3

ID Manipulation

Attacker replaces invoice ID with an ID belonging to a rival organization.

Step 4

Unscoped DB Query

Handler queries db.First(&invoice, "id = ?", paramID) without checking tenant organization ID.

Step 5

Data Leakage

Victim's confidential billing record is returned to the attacker.

Código Fuente: Vulnerable vs. Seguro

✕ IMPLEMENTACIÓN VULNERABLE
// VULNERABLE: Queries solely by client-supplied ID without tenant filter
func GetInvoice(c *gin.Context) {
    invoiceID := c.Param("id")
    var invoice Invoice

    // Vulnerable: trusts client ID without checking authenticated user's organization!
    if err := db.First(&invoice, "id = ?", invoiceID).Error; err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "Invoice not found"})
        return
    }

    c.JSON(http.StatusOK, invoice) // Leaks invoices across tenant boundaries!
}
✓ PARCHE SEGURO Y ROBUSTO
// HARDENED: Query is strictly scoped to authenticated user's organization ID
func GetInvoice(c *gin.Context) {
    invoiceID := c.Param("id")
    orgID, exists := c.Get("organization_id")
    if !exists {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
        return
    }

    var invoice Invoice
    // Defense-in-depth: query filters by BOTH invoice ID and authenticated org ID
    if err := db.Where("id = ? AND organization_id = ?", invoiceID, orgID).First(&invoice).Error; err != nil {
        // Return 404 rather than 403 to prevent ID enumeration
        c.JSON(http.StatusNotFound, gin.H{"error": "Invoice not found"})
        return
    }

    c.JSON(http.StatusOK, invoice)
}

Lista de Verificación de Seguridad para Ingeniería

References