미니프로젝트 DAY2(2)

aiden·6일 전

CSRF 취약점

CSRF 차단 코드

기본 아이디어

서버(app.py)

  • 세션에 csrf_token을 하나 만들어 둔다.
  • 모든 POST/PUT/DELETE 요청이 들어올 때, 요청 안에 실린 토큰이 세션에 저장된 값이랑 같은지 체크
  • 다른 경우 공격으로 보고 400 에러 발생

템플릿(HTML)

  • <form method="post">마다 <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    토큰을 함께 보낸다.

→ 상품삭제, 회원탈퇴, 글수정, 채팅 전송 등 모든 상태 변경 요청이 CSRF 보호를 받게 됨.

코드 작성

  1. import 추가

    app.py 맨 위쪽 import 아랫부분에 추가

    import secrets
    from flask import abort
  2. CSRF 토큰 생성 & 템플릿에서 사용

    app = Flask(name) 아래 설정(config) 직후에 추가

    # ---------------- CSRF 공통 설정 ----------------
    
    def generate_csrf_token():
        """세션에 csrf_token이 없으면 하나 생성해서 돌려주는 함수"""
        token = session.get("csrf_token")
        if not token:
            token = secrets.token_hex(32)  # 64자리 정도의 랜덤 문자열
            session["csrf_token"] = token
        return token
    
    @app.context_processor
    def inject_csrf_token():
        """모든 템플릿에서 csrf_token() 을 쓸 수 있게 주입"""
        return dict(csrf_token=generate_csrf_token)
  3. 미들웨어(before_request)로 전체 요청 검사

    위 코드의 바로 아래에 모든 POST/PUT/PATCH/DELETE 요청이 들어올 때 CSRF 토큰을 검사하는 코드를 추가

    # CSRF 검사용 예외 경로 (원하면 조절 가능)
    CSRF_EXEMPT_ENDPOINTS = {
        'health',        # /health 같은 단순 체크용
        # 'some_api'     # 필요하면 여기 추가
    }
    
    @app.before_request
    def csrf_protect():
        # GET, HEAD, OPTIONS, TRACE 는 안전한 메서드로 보고 검사 안 함
        if request.method in ("GET", "HEAD", "OPTIONS", "TRACE"):
            return
    
        # health 같은 것 제외하고 검사
        if request.endpoint in CSRF_EXEMPT_ENDPOINTS:
            return
    
        session_token = session.get("csrf_token")
    
        # JSON 요청인지, 일반 form인지에 따라 토큰 위치가 다를 수 있음
        form_token = request.form.get("csrf_token")  # 일반 form
        header_token = request.headers.get("X-CSRFToken") or request.headers.get("X-CSRF-Token")
    
        token = form_token or header_token
    
        if not session_token or not token or not secrets.compare_digest(str(session_token), str(token)):
            # 로그 찍고 차단
            print("[CSRF BLOCKED] method=", request.method, "path=", request.path, 
                  "form_token=", form_token, "header_token=", header_token)
            abort(400, description="CSRF token missing or invalid")
    

    → 이제부터는 로그인, 글 작성 등 모든 POST 계열 요청이 반드시 CSRF 토큰을 포함해야 통과됨.

  1. 템플릿에 토큰 추가(폼 전체에 적용)

    상품 등록, 상품 수정, 상품 삭제 form, 회원가입, 로그인 등 POST를 사용하는 모든 폼에 다음과 같이 코드 한 줄을 추가.

    <form method="POST" ...>
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        ...
    </form>

    product_new.html:

    <form method="POST" enctype="multipart/form-data">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        <!-- 나머지 input들 -->
    </form>

    product_edit.html:

    <form method="POST" enctype="multipart/form-data">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        ...
    </form>

    product_detail.html(상품 삭제 form):

    <form action="/products/{{ product.id }}/delete" method="POST" 
          onsubmit="return confirm('정말로 이 상품을 삭제하시겠습니까?');" style="margin-top: 0;">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        <button type="submit" class="contact-btn" ...>
            🗑️ 상품 삭제하기
        </button>
    </form>

    회원가입/로그인 폼에도 똑같이 한 줄 추가

4번 절차까지 완료하면 아까까지 잘 들어가졌던 공격 html 접속 시 다음과 같은 400 Bad Request 에러가 발생함

profile
파인애플 좋아하세요?

0개의 댓글