flawopen.com/bola-idor/Python

● CWE-639 · Kritis
Riset Keamanan · FlawOpen

Kerentanan Otorisasi Tingkat Objek (BOLA / IDOR) di Python

Hilangkan kerentanan BOLA (CWE-639) dan IDOR di Python dan FastAPI dengan membatasi kueri basis data langsung ke identitas organisasi pengguna yang terotentikasi.

💡 Penjelasan Sederhana (ELI5)

Seperti kunci hotel kamar 204 yang bisa membuka kamar 205 jika nomor pintunya diubah. Hotel memverifikasi bahwa Anda adalah tamu (otentikasi), tetapi lupa memeriksa apakah kamar 205 adalah milik Anda (otorisasi objek).

Konsep Kunci & Istilah

Authentication vs. Authorization
Autentikasi mengonfirmasi SIAPA penggunanya (login yang valid). Otorisasi mengonfirmasi SUMBER DAYA APA yang diizinkan untuk dilihat atau diubah oleh pengguna tersebut.
BOLA (Broken Object Level Authorization)
Kerentanan OWASP API #1 di mana titik akhir mengekspos referensi objek (misalnya /api/invoices/1042) tanpa memverifikasi kepemilikan penyewa (BOLA).
IDOR (Insecure Direct Object Reference)
Kelas kerentanan yang lebih luas (CWE-639) di mana objek implementasi internal diekspos langsung ke pengguna tanpa pemeriksaan kontrol akses (IDOR).
Tenancy Scope Injection
Pola arsitektur di mana kueri database secara otomatis menerapkan WHERE organization_id = :auth_user_org_id pada lapisan ORM.
Non-Enumerable Identifiers (UUIDv4)
Menggunakan pengidentifikasi 128-bit acak secara kriptografis alih-alih bilangan bulat kenaikan otomatis berurutan untuk mencegah enumerasi yang dapat diprediksi.

Alur Serangan Langkah demi Langkah

Step 1

Legitimate Sign-In

An attacker creates a legitimate account on the platform and receives a valid JWT authentication bearer token.

Step 2

Resource Inspection

The attacker accesses their own billing invoice via GET /api/invoices/1042 and observes sequential database identifiers in use.

Step 3

Identifier Perturbation

The attacker modifies the URL to request GET /api/invoices/1041 using their own valid authentication token.

Step 4

Flawed Controller Logic

The FastAPI backend confirms the token is valid, but queries the database solely by Invoice.id == 1041 without verifying tenant ownership.

Step 5

Cross-Tenant Exfiltration

The server returns confidential invoice records, billing details, and personal data belonging to another organization.

Step 6

Automated Harvest

The attacker scripts a loop iterating over IDs 1 through 100,000, draining the entire multi-tenant database.

Kode Sumber: Rentan vs Aman

✕ IMPLEMENTASI RENTAN
# VULNERABLE: Verifies user login, but queries object solely by client ID
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session

@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    # Flaw: Attacker passes invoice_id belonging to another tenant
    invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
    
    if not invoice:
        raise HTTPException(status_code=404, detail="Invoice not found")
        
    # Leaks confidential billing records of competitor organizations!
    return invoice
✓ PERBAIKAN AMAN & KUAT
# HARDENED: Query is strictly bound to the authenticated tenant's organization ID
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session

@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    # Defense-in-depth: query filters by BOTH invoice_id AND authenticated organization_id
    invoice = db.query(Invoice).filter(
        Invoice.id == invoice_id,
        Invoice.organization_id == current_user.organization_id
    ).first()
    
    if not invoice:
        # Return 404 rather than 403 to prevent object existence enumeration
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Invoice not found")
        
    return invoice

Daftar Periksa Penguatan Sistem Rekayasa

References