flawopen.com/bola-idor/Go

● CWE-639 · 严重
安全研究 · 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.

💡 通俗易懂的原理解析 (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.

核心概念与专有名词

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)).

攻击执行流程分解

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.

源代码对比:漏洞与安全实现

✕ 存在漏洞的实现
// 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!
}
✓ 加固后的安全修复
// 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)
}

工程与系统安全加固清单

References