전체 그래프
OWASP Top 10

IDOR

web-securityowaspidoraccess-controlattack

상위: Web Security

요약

IDOR(Insecure Direct Object Reference, 안전하지 않은 직접 객체 참조)는 사용자가 권한 검증 없이 다른 사용자의 리소스에 접근할 수 있는 취약점입니다. URL이나 파라미터의 ID 값을 변경하여 타인의 데이터를 조회/수정/삭제할 수 있습니다.

공격 원리

정상 요청:
GET /api/users/123/profile  ( 프로필)

IDOR 공격:
GET /api/users/124/profile  (다른 사용자 프로필)
GET /api/users/125/profile
GET /api/users/1/profile    (관리자?)

핵심 문제: 서버가 "요청한 리소스에 접근 권한이 있는가?"를 확인하지 않음

공격 유형

1. 수평적 권한 상승 (Horizontal)

같은 권한 레벨의 다른 사용자 데이터 접근

사용자 A (user_id=100)
 GET /orders/500 (A의 주문)
 GET /orders/501 (B의 주문)  IDOR!

결과: 다른 사용자의 주문 정보, 결제 내역, 개인정보 노출

2. 수직적 권한 상승 (Vertical)

더 높은 권한의 기능/데이터 접근

일반 사용자
 GET /admin/users  관리자 페이지 접근
 POST /api/users/123/role {"role": "admin"}  권한 상승

결과: 관리자 기능 사용, 권한 변경

3. 파일 기반 IDOR

다운로드:
GET /download?file=report_user123.pdf
GET /download?file=report_user124.pdf  IDOR!

업로드된 파일:
GET /uploads/invoice_12345.pdf
GET /uploads/invoice_12346.pdf  IDOR!

실제 공격 시나리오

시나리오 1: 전자상거래 주문 조회

취약한 API:

GET /api/orders/10001

공격:

# 주문 번호 순회
for i in {10001..10100}; do
    curl "https://shop.com/api/orders/$i" -H "Cookie: session=xxx"
done

결과:

  • 타인의 주문 내역
  • 배송 주소, 연락처
  • 결제 정보

시나리오 2: 은행 계좌 조회

GET /api/accounts/1234567890/balance

공격:
GET /api/accounts/1234567891/balance
GET /api/accounts/0000000001/balance  (특별 계좌?)

시나리오 3: 의료 기록

GET /patient/records?patient_id=P12345

공격:
GET /patient/records?patient_id=P12346
 타인의 의료 기록 노출 (HIPAA 위반)

시나리오 4: 문서/파일 공유

정상: https://docs.company.com/view/abc123xyz
공격: https://docs.company.com/view/abc123xyz (다른 문서 ID 추측)

취약한 코드 vs 안전한 코드

취약한 코드

@app.route('/api/orders/<order_id>')
def get_order(order_id):
    # 권한 검증 없음!
    order = Order.query.get(order_id)
    return jsonify(order.to_dict())

안전한 코드

@app.route('/api/orders/<order_id>')
@login_required
def get_order(order_id):
    order = Order.query.get(order_id)

    # 권한 검증
    if order.user_id != current_user.id:
        abort(403, "접근 권한이 없습니다")

    return jsonify(order.to_dict())

더 안전한 코드 (쿼리 레벨 필터링)

@app.route('/api/orders/<order_id>')
@login_required
def get_order(order_id):
    # 애초에 본인 주문만 조회 가능
    order = Order.query.filter_by(
        id=order_id,
        user_id=current_user.id  # 사용자 필터 포함
    ).first_or_404()

    return jsonify(order.to_dict())

방어 방법

1. 접근 제어 검증 (필수)

def check_access(user, resource):
    # 소유자 확인
    if resource.owner_id == user.id:
        return True
    # 관리자 확인
    if user.is_admin:
        return True
    # 공유된 리소스 확인
    if user.id in resource.shared_with:
        return True
    return False

2. 간접 참조 사용

# 직접 참조 (취약)
GET /api/users/12345

# 간접 참조 (안전)
GET /api/users/me  # 세션에서 사용자 식별

3. UUID 사용 (추측 어렵게)

import uuid

# 순차적 ID (취약)
order_id = 12345  # 쉽게 추측 가능

# UUID (안전)
order_id = uuid.uuid4()  # 550e8400-e29b-41d4-a716-446655440000

주의: UUID만으로는 불충분! 여전히 권한 검증 필요

4. 서명된 URL (파일 접근)

import hmac
import time

def generate_signed_url(file_id, user_id, expires_in=3600):
    expiry = int(time.time()) + expires_in
    signature = hmac.new(
        SECRET_KEY,
        f"{file_id}:{user_id}:{expiry}".encode(),
        'sha256'
    ).hexdigest()

    return f"/download/{file_id}?user={user_id}&expires={expiry}&sig={signature}"

5. 레코드 레벨 보안

# Django
class OrderQuerySet(models.QuerySet):
    def for_user(self, user):
        if user.is_admin:
            return self
        return self.filter(user=user)

class Order(models.Model):
    objects = OrderQuerySet.as_manager()

# 사용
orders = Order.objects.for_user(request.user).all()

테스트 방법

1. ID 값 변경

원본: /api/users/100
테스트: /api/users/101, /api/users/99, /api/users/1

2. 파라미터 조작

원본: /orders?user_id=100
테스트: /orders?user_id=101
테스트: /orders (user_id 제거)

3. HTTP 메서드 변경

GET /api/users/101  (읽기)
PUT /api/users/101  (수정)
DELETE /api/users/101  (삭제)

4. 자동화 도구

# Burp Suite Intruder
# 또는 간단한 스크립트
for id in $(seq 1 1000); do
    response=$(curl -s "https://api.example.com/users/$id" -H "Authorization: Bearer $TOKEN")
    if [ ! -z "$response" ]; then
        echo "ID $id: $response"
    fi
done

IDOR 발생하기 쉬운 기능

기능파라미터 예시
사용자 프로필/users/{user_id}
주문 조회/orders/{order_id}
파일 다운로드/download?file_id=123
메시지/댓글/messages/{msg_id}
결제 내역/payments/{payment_id}
API 키 관리/api-keys/{key_id}
설정 페이지/settings?user=123

관련 공격

참고 자료