서버(app.py)
템플릿(HTML)
<form method="post">마다 <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">→ 상품삭제, 회원탈퇴, 글수정, 채팅 전송 등 모든 상태 변경 요청이 CSRF 보호를 받게 됨.
import 추가
app.py 맨 위쪽 import 아랫부분에 추가
import secrets
from flask import abort
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)
미들웨어(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 토큰을 포함해야 통과됨.
템플릿에 토큰 추가(폼 전체에 적용)
상품 등록, 상품 수정, 상품 삭제 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 에러가 발생함
