장고는 안전한 웹 애플리케이션을 구축하는 데 사용할 수 있는 Python 웹 프레임워크입니다. 개발자의 보안에 도움이 되는 많은 기능을 제공합니다. 이러한 기능 중 하나는 CSRF 토큰으로, 사이트 간 요청 위조 공격으로부터 양식을 보호하는 데 필수적입니다.
CSRF 토큰이란 무엇인가요?
CSRF 토큰은 크로스 사이트 요청 위조(CSRF) 공격으로부터 웹 애플리케이션을 보호하는 보안 기능입니다. 이를 통해 애플리케이션 서버는 양식 제출이 진짜 브라우저에서 온 것인지 아니면 해커가 위조한 것인지 확인할 수 있습니다.
CSRF 토큰은 사용자 세션을 추적하는 양식 입력입니다. 웹사이트의 서버 측 웹 애플리케이션 프레임워크는 일반적으로 각 고유 사용자 세션에 대해 CSRF 토큰을 생성합니다. 서버는 사용자가 양식을 제출할 때마다 토큰이 올바른지 확인합니다. CSRF 토큰은 일반적으로 임의의 문자열과 숫자로 구성되므로 값을 예측할 수 없습니다.
장고에서 CSRF 토큰 생성
장고의 get_token() 함수는 CSRF 토큰을 무작위로 생성합니다. 이 함수를 찾으려면 Python 가상 환경 내에서 csrf.py 파일로 이동하세요. 폴더 구조는 다음과 같아야 합니다:
env/
└── Lib/
└── site-packages/
└── django/
└── middleware/
└── csrf.py
이 파일 안에 토큰을 반환하는 get_token() 함수를 찾을 수 있습니다. Django는 데이터 마스킹을 사용하여 해커로부터 토큰의 값을 보호합니다.
기본적으로 Django는 settings.py 파일의 MIDDLEWARE 목록에 django.middleware.csrf.CsrfViewMiddleware를 추가하여 사이트에 대한 CSRF 보호를 사용하도록 설정합니다. POST 양식에 {% csrf_token %}을 추가하기만 하면 됩니다. csrf_token %}을 추가하지 않으면 양식을 제출할 때 403(금지됨) 오류가 발생합니다.
양식에 {% csrf_token %}을 추가하면 숨겨진 입력 필드가 자동으로 생성되며, 이 필드에는 마스킹된 CSRF 토큰의 값이 포함되어 있습니다. 서버는 이 값을 사용하여 양식 제출이 진짜인지 여부를 판단합니다. 페이지 소스를 보거나 브라우저의 개발자 도구 기능을 사용하여 이 숨겨진 필드의 값을 확인할 수 있습니다.
장고에서 CSRF 토큰이 작동하는 방식
양식을 사용하여 사이트를 시작하면 장고는 자동으로 csrftoken이라는 브라우저 쿠키를 생성합니다. 이 쿠키는 사이트에서의 사용자 활동을 추적하고 각 사용자를 고유하게 식별합니다.
사용자가 양식을 제출하면 서버는 쿠키의 값을 숨겨진 입력 필드에 있는 csrfmiddlewaretoken의 값과 비교합니다. 이 값이 일치하면 서버는 양식을 성공적으로 처리하고, 그렇지 않으면 오류를 생성합니다.
언뜻 보기에는 쿠키와 csrfmiddlewaretoken의 값이 다른 것처럼 보입니다. 이는 의도적인 것으로 CSRF 토큰에 추가적인 보호 계층을 추가합니다. CSRF 토큰은 다음과 같이 쿠키와 비교됩니다:
⭐ get_token() 함수는 CSRF 토큰을 마스킹한 후 입력 필드에 전달합니다.
⭐ 양식이 제출되면 설정 파일에 있는 비밀 키를 사용하여 CSRF 토큰의 마스킹이 해제됩니다.
⭐ 마스킹이 해제된 토큰을 세션 쿠키와 비교합니다.
⭐ 값이 같으면 양식이 처리됩니다. 그렇지 않으면 서버가 오류를 반환합니다.
해커가 CSRF 토큰을 도용하는 것을 방지하기 위해 Django는 사용자 세션을 시작할 때마다 토큰을 갱신합니다.
사용자 지정 CSRF 토큰 만들기
장고에서는 {% csrf_token %}를 추가하기만 하면 폼을 쉽게 보호할 수 있지만, CSRF 토큰을 생성하여 폼에 수동으로 추가하는 것도 가능합니다. 이렇게 하려면 get_token() 함수를 가져옵니다:
from django.middleware.csrf import get_token
보기에서 다음과 같이 CSRF 토큰을 생성할 수 있습니다:
def view_name(request):
csrf_token = get_token(request)
# perform view logic
context = {
"csrf_token": csrf_token
}
return render(request, 'app_name/template.html', context=context)
HTML 템플릿에서 입력 태그를 수동으로 포함시키고 다음과 같이 csrf_token을 추가할 수 있습니다:
<form method="POST" >
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
{{form.as_p}}
<button type="submit" class="btn btn-outline-secondary">Add Book</button>
</form>
또는 다음과 같이 뷰에서 숨겨진 입력 필드를 생성할 수 있습니다:
def your_view(request):
csrf_token = get_token(request)
csrf_token_html = '<input type="hidden" name="csrfmiddlewaretoken" value="{}" />'.format(csrf_token)
# perform view logic
context = {
"csrf_token": csrf_token_html
}
return render(request, 'app_name/template.html', context=context)
그런 다음 다음과 같이 HTML 템플릿에 추가할 수 있습니다:
<form method="POST" >
{{ csrf_token_html|safe }}
{{form.as_p}}
<button type="submit" class="btn btn-outline-secondary">Add Book</button>
</form>
양식의 CSRF 보호를 완전히 제어하려면 CSRF 토큰을 브라우저에 저장된 쿠키와 비교하여 제어할 수 있습니다. 비교 결과에 따라 원하는 대로 양식 제출을 처리할 수 있습니다. 다음은 예시입니다:
from django.shortcuts import render
from django.middleware.csrf import get_token, _unmask_cipher_token
from django.utils.crypto import constant_time_compare
def your_view(request):
# Generate a custom CSRF token
csrf_token = get_token(request)
csrf_cookie = request.COOKIES.get('csrftoken')
# unmask csrf token
unmasked_csrf_token = _unmask_cipher_token(csrf_token)
# Compare the tokens
if not constant_time_compare(unmasked_csrf_token, csrf_cookie):
# Handle the case where the tokens don't match
pass
else:
# Handle the case where the tokens match
pass
# Render the template
context = {
'csrf_token': csrf_token,
}
return render(request, 'app_name/template.html', context=context)
이 코드 스니펫은 HTTP 요청 객체에서 csrf_cookie를 검색합니다. 그런 다음 _unmask_cipher_token() 함수를 사용하여 csrf_token의 마스크를 해제합니다.
조건문은 검색된 csrf_cookie와 마스킹이 해제된 csrf_token의 값을 비교합니다. 이 비교는 상수_시간_비교 함수를 사용하여 타이밍 익스플로잇으로부터 보호합니다. 비교 결과에 따라 로직을 작성할 수 있습니다.
장고에서 CSRF 보호 비활성화하기
장고에서 CSRF 보호를 기본적으로 제공하지만, 원하는 경우 프로젝트에서 비활성화할 수 있습니다. 이를 수행하는 방법에는 두 가지가 있습니다:
⭐ 전체 웹사이트에서 CSRF 보호를 비활성화합니다.
⭐ 특정 보기에서 CSRF 보호를 비활성화합니다.
전체 웹 사이트에서 CSRF 보호 비활성화
웹 사이트에서 Django의 CSRF 보호를 비활성화하려면 설정 파일에서 CSRF 미들웨어를 제거하기만 하면 됩니다. 설정 파일에서 미들웨어라는 목록을 찾습니다. 목록에서 다음을 검색합니다:
'django.middleware.csrf.CsrfViewMiddleware',
이 미들웨어를 찾으면 코드에서 제거하여 Django의 기본 CSRF 보호 기능을 비활성화해야 합니다.
특정 뷰에서 CSRF 보호 비활성화
특정 Django 뷰에서만 CSRF 보호를 비활성화하려면 @csrf_exempt 데코레이터를 사용하세요. 다음은 데모를 위한 코드 스니펫입니다:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def view_name(request):
# perform view logic
pass
@csrf_exempt 데코레이터는 장고에서 CSRF 보호와 관련된 여러 가지 데코레이터 중 하나에 불과합니다. 나머지는 Django의 CSRF 참조 에서 확인할 수 있습니다.
웹 사이트에서 CSRF 보호를 비활성화하지 마세요
장고로 가능하지만 장고의 기본 제공 CSRF 보호 메커니즘을 비활성화하는 것은 권장되지 않습니다. 그렇게 하면 사이트가 CSRF 공격에 취약해지고 궁극적으로 앱 사용자에게 부정적인 영향을 미치게 됩니다.
사용자 지정 CSRF 보호 메커니즘을 구현하는 방법을 알고 있는 숙련된 개발자가 아니라면 Django에서 제공하는 대안을 사용하여 작업해야 합니다.