flawopen.com/bola-idor/Python

● CWE-639 · 심각
보안 연구 · FlawOpen

Python 환경의 객체 수준 권한 부여 취약점 (BOLA / IDOR) 방어

FastAPI 및 Python 환경에서 BOLA (CWE-639) / IDOR 취약점을 근절하는 방법: 클라이언트가 전달한 객체 ID를 맹신하지 않고 인증된 조직 테넌트 ID를 쿼리에 강제 결합하는 실무 가이드.

💡 알기 쉬운 설명 (ELI5)

호텔 204호에 투숙했는데 문 손잡이에 205를 적기만 해도 옆 방 문이 열리는 상황과 같습니다. 호텔은 투숙객 신원(인증)은 확인했지만, 해당 방의 소유자(객체 권한 부여)인지는 확인하지 않았습니다.

핵심 개념 및 용어

Authentication vs. Authorization
보안 개념 (Authentication vs. Authorization): Authentication confirms WHO the user is (valid login). Authorization confirms WHAT resources that specific user is permitted to view or modify.
BOLA (Broken Object Level Authorization)
보안 개념 (BOLA (Broken Object Level Authorization)): The #1 OWASP API vulnerability where an endpoint exposes an object reference (e.g. /api/invoices/1042) without verifying tenant ownership.
IDOR (Insecure Direct Object Reference)
보안 개념 (IDOR (Insecure Direct Object Reference)): The broader vulnerability class (CWE-639) where internal implementation objects are exposed directly to users without access control checks.
Tenancy Scope Injection
보안 개념 (Tenancy Scope Injection): An architectural pattern where database queries automatically enforce WHERE organization_id = :auth_user_org_id at the ORM layer.
Non-Enumerable Identifiers (UUIDv4)
보안 개념 (Non-Enumerable Identifiers (UUIDv4)): Using cryptographically random 128-bit identifiers instead of sequential auto-incrementing integers (1, 2, 3...) to prevent predictable enumeration.

단계별 공격 실행 흐름

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.

소스 코드 비교: 취약한 구현 vs 보안 패치

✕ 취약한 구현
# 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
✓ 보안 강화 패치
# 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

엔지니어링 및 시스템 보안 강화 체크리스트

References