애플리케이션 보안

Broken Access Control

meellon 2026. 6. 28. 06:00

학습 목표

Broken Access Control이 OWASP Top 10 1위 취약점 클래스임을 설명할 수 있다.

IDOR(Insecure Direct Object Reference)에서 객체 ID만 바꿔 타인 리소스에 접근하는 방식을 설명할 수 있다.

수평(같은 role·다른 리소스)과 수직(낮은 role·높은 권한 API) 권한 상승을 구분할 수 있다.

Mass assignment·missing function-level access control·forced browsing 패턴을 설명할 수 있다.

서버 측 매 요청 검증·deny by default·자동 IDOR 테스트로 방어할 수 있다.

문제 상황

  • 로그인한 일반 사용자GET /api/invoices/1001200 + 남의 청구서
    • invoice_id만 순차 증가 — owner 검증 없음
  • SPA에서 /admin URL 직접 입력 → 대시보드 노출
    • 프론트 라우트 가드만, API는 인증만 확인
  • PATCH /api/profile body에 "role": "admin" — DB 그대로 반영
    • ORM update(**request.json) 전 필드
  • JWT role: member인데 DELETE /api/users/5 200
    • 함수·엔드포인트 단위 permission 누락
  • GET /api/files?path=../../../etc/passwd경로 검증 없음
  • GraphQL users { email passwordHash }필드 수준 인가 없음

인증(앞서 4~7편)은 통과했지만 인가가 깨진다. Injection·XSS다름 — 입력 조작이 아니라 권한 경계 실패.

본 글의 시나리오·예제는 방어·테스트 학습 목적이다. 타인 데이터 무단 접근은 불법이다.

1. Broken Access Control이란

Broken Access Control — 사용자가 허용되지 않은 행동·리소스에 접근 (OWASP A01:2021 · CWE-284 계열).

정상:  authenticated user  →  policy allow  →  resource
실패:  authenticated user  →  check missing  →  others data / admin API
OWASP Top 10 (2021) 비고
A01 Broken Access Control 1위 — 설계·구현·테스트 모두
A03 Injection 코드·쿼리 조작
A07 Identification and Authentication Failures 누구인지 실패
  • 앞서 인가 모델 — RBAC·ABAC·ownership은 이 실패를 막는 설계 층
  • 401 vs 403 — 인증 실패 vs 인가 거부 (혼동 시 디버그·공격면 확대)

2. IDOR

IDOR — API·URL의 직접 참조(orderId, fileId)만 바꿔 다른 객체 접근.

# 취약 — code/idor-vulnerable.py
@app.get("/api/orders/{order_id}")
def get_order(order_id: int, user=Depends(auth)):
    return db.query("SELECT * FROM orders WHERE id = ?", order_id)
    # owner_id 검증 없음 — ID만 바꾸면 타인 주문

공격:

GET /api/orders/999 HTTP/1.1
Authorization: Bearer <member_token>
# order 999는 다른 사용자 소유 — 취약 시 200

방어

# 안전 — code/idor-safe.py
@app.get("/api/orders/{order_id}")
def get_order(order_id: int, user=Depends(auth)):
    order = db.get_order(order_id)
    if order is None:
        raise HTTPException(404)
    if order.owner_id != user.id and not user.has_permission("orders:read_any"):
        raise HTTPException(403)
    return order
방어 설명
Ownership check resource.owner_id == sub
쿼리 필터 list: WHERE owner_id = :sub
UUID 순차 ID 추측 어렵지만 검증 없으면 여전히 IDOR
404 vs 403 존재·권한 정보 노출 트레이드오ff — 팀 규칙 일관
  • 앞서 ABAC — subject.user_id == resource.owner_id 정책
  • BFF·GraphQL — resolver 마다 동일 검증

3. 수평·수직 권한 상승

유형 정의
수평 (Horizontal) 같은 privilege · 다른 주체 리소스 A가 B의 /orders/999
수직 (Vertical) 낮은 role · 높은 권한 기능 member → /admin/users DELETE
수평:  role(member) + resource(owner=B)  →  deny
수직:  role(member) + action(admin)      →  deny

Missing function-level access control

엔드포인트·HTTP 메서드마다 permission 없음 — "로그인만 하면" admin API.

# 취약 — code/missing-function-auth-vulnerable.example
@app.delete("/api/admin/users/{user_id}")
def delete_user(user_id: int, user=Depends(auth)):
    db.delete_user(user_id)  # role check missing
# 안전 — code/missing-function-auth-safe.example
@app.delete("/api/admin/users/{user_id}")
def delete_user(user_id: int, user=Depends(auth)):
    if not user.has_permission("users:delete"):
        raise HTTPException(403)
    db.delete_user(user_id)
  • Forced browsing — 숨겨진 URL·Swagger /admin 직접 호출
  • 프론트 메뉴 숨김 ≠ 인가 — API에서 매 요청 검증

4. Mass assignment

클라이언트가 보내지 말아야 할 필드를 body에 넣어 권한·상태 변경.

# 취약 — code/mass-assignment-vulnerable.example
@app.patch("/api/profile")
def update_profile(body: dict, user=Depends(auth)):
    user.update(**body)  # body may include role, balance, is_verified

공격 body:

{ "display_name": "Alice", "role": "admin", "credit_balance": 99999 }
# 안전 — code/mass-assignment-safe.example
ALLOWED = {"display_name", "avatar_url"}

@app.patch("/api/profile")
def update_profile(body: dict, user=Depends(auth)):
    data = {k: v for k, v in body.items() if k in ALLOWED}
    user.update(**data)
방어  
Allowlist 필드 DTO·Pydantic model 명시
Separate admin API role 변경 전용 엔드포인트 + 강한 인가
Read-only 속성 ORM에서 role 쓰기 차단

5. 기타 패턴

패턴 방어
Path traversal file=../../etc/passwd canonical path + chroot
Tenant bleed tenant A token → tenant B data tenant_id 쿼리·JWT 교차
Metadata tampering X-User-Id: 1 헤더 서버 identity만 신뢰
GraphQL over-fetch passwordHash 필드 필드 수준 auth
CORS misconfig Access-Control-Allow-Origin: * + credentials origin allowlist
# tenant — list API
orders = db.query(Order).filter(
    Order.tenant_id == user.tenant_id,
    Order.owner_id == user.id,
)
  • JWT claimsrole·tenant 참고; DB·policy 최종 (앞서 인가 모델)
  • PostgreSQL RLS — DB 수준 — 앱 버그 2차 방어

6. 방어 계층

계층 역할
Deny by default 명시 allow만 — RBAC 기본 거부
Per-request server check 매 API·resolver
Ownership + permission IDOR + vertical 동시
Policy engine / middleware 중앙 정책·fail closed
RLS / ORM scope 데이터 계층 필터
Automated tests IDOR·role matrix CI
Defense in depth (2편):
  UI hide admin menu     ✗
  server authz + tests   ✓
  • 403/404 일관 — 정보 수집 vs UX
  • Audit log — admin·deny 이벤트 (감사는 이후 운영 보안 편)

7. 테스트·체크리스트

테스트 방법
IDOR user A token → user B 모든 ID 엔드포인트
Vertical member token → admin API 목록 (Swagger)
Mass assignment PATCH에 privilege 필드 주입
HTTP method GET으로 delete 가능한지
Tenant cross-tenant ID 전수
# unit test sketch — code/idor-test.example
def test_member_cannot_read_others_order(client, user_a, user_b_order_id):
    r = client.get(f"/api/orders/{user_b_order_id}", headers=user_a.auth)
    assert r.status_code in (403, 404)
체크  
모든 ID API owner 또는 read_any permission
Admin prefix 별도 permission + MFA (선택)
GraphQL 필드·타입 정책
Default deny all 새 엔드포인트
CI authz matrix · OWASP ZAP (staging)
안티패턴  
UUID만으로 안전 검증 없으면 IDOR
JWT role만 철회·변경 지연
200 + empty 403/404 명확
"내부 API" 신뢰 경계 동일

8. 정리

  • Broken Access Control — OWASP 1위인증 ≠ 인가
  • IDOR — 객체 ID + missing ownership — UUID도 검증 필수
  • 수평·수직 — 리소스 vs 기능 권한 — 엔드포인트 검증
  • Mass assignmentallowlist 필드
  • 서버 매 요청 + deny default + 자동 테스트

다음에 다룰 것

  • 기타 OWASP Top 10
  • SSRF, misconfiguration, vulnerable components

해당 내용은 OWASP Top 10, OWASP Authorization Cheat Sheet, CWE-639 (Authorization Bypass Through User-Controlled Key), CWE-269 (Improper Privilege Management) 의 내용을 기반으로 합니다.

'애플리케이션 보안' 카테고리의 다른 글

시크릿 관리  (0) 2026.06.30
OWASP Top 10  (0) 2026.06.29
XSS와 CSRF  (0) 2026.06.27
Injection  (0) 2026.06.26
인가 모델  (0) 2026.06.25