학습 목표
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/1001→ 200 + 남의 청구서invoice_id만 순차 증가 — owner 검증 없음
- SPA에서
/adminURL 직접 입력 → 대시보드 노출- 프론트 라우트 가드만, API는 인증만 확인
PATCH /api/profilebody에"role": "admin"— DB 그대로 반영- ORM
update(**request.json)전 필드
- ORM
- JWT
role: member인데DELETE /api/users/5200- 함수·엔드포인트 단위 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 claims —
role·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 assignment — allowlist 필드
- 서버 매 요청 + 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) 의 내용을 기반으로 합니다.