안수찬 블로그

Django와 Rails에서 CSRF Token의 동작 방식

Introduction

안수찬 @dobestan

서울대학교에서 컴퓨터공학을 전공하고, 오랜 기간 서비스 기획 및 개발을 해 왔습니다. 이러한 전문성을 인정받아 미래부 소프트웨어 마에스트로에 선정된 바 있습니다. 현재는 모바일 방송국, 퍼스트캔버스에서 컨텐츠로 새로운 가치를 그리고 있습니다. 나는 안수찬이다. 그러므로 나는 할 수 있다. me@ansuchan.com


Django와 Rails에서 CSRF Token의 동작 방식

Posted by 안수찬 @dobestan on .
Featured

Django와 Rails에서 CSRF Token의 동작 방식

Posted by 안수찬 @dobestan on .

본 포스팅은 CSRF(CrossSite Request Forgery)가 무엇인지 안다고 가정하고 서술되었습니다. CSRF에 대해서는 Wikipedia > CSRF에서 자세히 살펴보실 수 있습니다.

최근에 소프트웨어 마에스트로 프로젝트를 진행하면서 Django를 조금 사용해보고 있다. Ruby on Rails과 비슷한 부분도 많지만 다른 부분도 많아 비교하면서 이해하는 방식이 정말 도움이 된다. 개인적으로 좀 더 우아한 테스트 코드를 짜고 싶어서 요새는 브라우져에서 테스트하기 보다는 curl로 Request를 보내서 확인하는 방식을 시도해보고 있다.

그러던 와중에 CSRF Token 때문에 기본적인 curl 명령어나 AJAX 통신을 하는데 문제가 발생했다. 레일즈와 장고를 비교하면서 간단하게 살펴보자. 사실 두 프레임워크 모두 CSRF를 막기 위해서 Synchronizer Token PatternCSRF Token Cookie 두 가지 방식을 사용하기에 거의 유사하다. 다만 레일즈는 쿠키에 세션 정보를 합쳐서 APP_NAME_session이라는 쿠키를 만들기 때문에 조금 차이가 있다.

개요

사실 CSRF를 막는 방법은 다양하다. 우리가 한국형 사이트에서 자주 보는 CAPTCHA 코드를 그대로 적는 방식도 CSRF를 막는 좋은 방법이다. 또한 데이터의 중요한 변경이 있는 경우에 사용자의 비밀번호를 한번 더 묻는 방식도 크게 보면 CSRF 예방법에 속한다. 여기서는 장고, 레일즈 두 프레임워크에서 기본적으로 제공하는 2가지 방식 Synchronizer Token PatterCSRF Token Cookie을 알아보자.

1. Synchronizer Token Pattern ( STP )

Synchronizer token pattern is a technique where a token, secret and unique for each request, is embedded by the web application in all HTML forms and verified on the server side. The token may be generated by any method that ensures unpredactibility and uniqueness. The attacker is thus unable to place a correct token in his requests to authenticate them.

사실 이 방식은 구현이 조금 복잡할 뿐이지 사용하는 입장에서는 굉장히 쉽다. 데이터를 받을 때 마다 ( 매 Request 마다 ) hidden form을 생성해서 csrf token을 함께 전달한다.

Django

<input type="hidden" name="csrfmiddlewaretoken" value="NLhO9haDAXsgOYFUo2Bt5zn5oeVQmj67">  

Rails

<meta content="authenticity_token" name="csrf-param">  
<meta content="bq3KGnNUBhedSFzUaajSWHnBErynInSks0uHlh6ifw0=" name="csrf-token">

<input name="authenticity_token" type="hidden" value="bq3KGnNUBhedSFzUaajSWHnBErynInSks0uHlh6ifw0=">  

How to POST data with curl?

이 방식만 사용할 경우에는 curl 명령어를 이용해서 POST Method를 보내기가 쉽다. 보내는 요청의 데이터 부분에 csrf_token만을 추가하면 된다.

user$ curl -X POST -d "username=dobestan&password=MY_PASSWORD&csrfmiddlewaretoken=CSRF_TOKEN" localhost/login  

하지만 데이터에 csrf_token을 추가하는 방식만으로는 동작하지 않는다. 장고와 레일즈 모두 쿠키를 이용한 CSRF Token Cookie를 함께 사용하기 때문이다. 아래에서 살펴보자.

2. CSRF Token Cookie

이 방법은 단독으로는 잘 사용되지 않는다. 일반적으로는 위의 Synchronizer Pattern Token과 함께 사용되는 경우가 많다. 처음으로 사이트에 방문하면 쿠키를 저장하는데 이 쿠키에는 csrf-token에 대한 정보가 저장되어 있다. 그리고 그 이후부터는 데이터를 전송할때 HTTP Header에 이 쿠키의 정보를 포함하여 전송한다.

Django

장고에서는 별도의 쿠기를 사용한다. 즉, csrftoken이라는 이름의 쿠키에 csrfmiddlewaretoken에 저장된 값과 동일한 값이 저장된다.

csrftoken       NLhO9haDAXsgOYFUo2Bt5zn5oeVQmj67  

Rails

레일즈도 사실 장고와 거의 비슷하나 한 단계를 더 거친다. 쿠키를 따로따로 저장하지 않고 첫 요청에 보내줄때 APP_NAME_session이라는 쿠키를 별도로 만들어 모든 쿠키를 합쳐서 보내준다. 그래서 우리가 이 쿠키를 통해서 별도의 csrftoken을 알아볼 수는 없다.

csrftoken       U3JZV2RWUUc4b0wxWXYzU0RiUFhxY0swRjE5VWNKRGhWZkxnTm91dWRNcDVUQXQzamRXZlhBYzdueWdxNUlPa0xMN2ZVWXVkSm1rNXlvb2ZCS3doY29PUGhoVmdDdGZwL2VVdk1hWVBraGtoRHI3dFRaVEg3TnZSY0VRRDcwUGwxU0ZzZm45bDg4MzNLZkUvaFgxMlFBPT0tLUtLNHhDeWZDUTVJTTJvOHRLUi9ZWHc9PQ  

How to POST data with curl?

장고를 기준으로 작성하였다. 레일즈도 유사한 방식으로 할 수 있다. 일단 curl -c 명령어로 쿠기를 가져와서 어떻게 구성되어 있는지 확인해보자.

user$ curl -c cookie.txt localhost:8000/blog  
user$ cat cookie.txt  
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

localhost       FALSE   /       FALSE   1437888553      csrftoken       Izo9zhafchnTkg5XxbU60Q0c8Xte96rB  

이번에는 curl 명령어에 두 가지를 추가한다.

  1. 데이터에 csrfmiddlewaretoken 추가하기
  2. HTTP Header에 쿠키를 함께 전송하기
user$ curl -c cookie.txt -b cookie.txt -d "csrfmiddlewaretoken=Izo9zhafchnTkg5XxbU60Q0c8Xte96rB&subject=test&writer=dobestan&content=nothing" localhost:8000/blog/ -v

...
> POST /blog/ HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8000
> Accept: */*
> Cookie: csrftoken=Izo9zhafchnTkg5XxbU60Q0c8Xte96rB
> Content-Length: 97
> Content-Type: application/x-www-form-urlencoded
...

이번에는 성공적으로 동작하고 헤더를 살펴보면 Cookie: csrftoken=Izo9zhafchnTkg5XxbU60Q0c8Xte96rB가 추가되었음을 알 수 있다. 웹 브라우져로 살펴보려면 크롬 개발자도구 > Networks > Headers에서 살펴볼 수 있다.

How about AJAX or Client Frameworks?

사실 curl 명령어를 이용해서 Request를 보낼 때는 위의 방법으로 해결할 수 있다. 하지만 Ajax 통신에서도 매번 이렇게 쿠키나 데이터를 보내야할까? 특히나 angular.jsember.js 같은 클라이언트 프레임워크를 사용할때는 어떻게 해결할 수 있을까?

사실 이 부분은 굉장히 간단하다. angular.js에서는 모든 Request에 기본적으로 HTTP Header에 특정 쿠키를 포함하여 전송하는 방법을 제공한다. 이 부분에 대한 자세한 설명은 이 포스팅의 범위를 넘어서는 것 같아 다음 포스팅에서 정리하도록 하겠다.

요약

레일즈와 장고를 살펴보면서 CSRF Token이 어떤식으로 동작하는지 알아보았다. 두 프레임워크 모두 CSRF를 막는 방법 중 2가지를 기본적으로 제공하였다. 첫 번째는 Synchronizer Pattern Token을 이용하여 매 Request에 hidden form을 추가하여 함께 전송하였다. 두 번째는 CSRF Token Cookie를 이용하여 매 Request에 HTTP Header에 토큰을 추가하여 전송하였다. 그래서 이 부분을 이해하고 curl을 이용해서 POST Method로 데이터를 전송하는데 성공하였다. 또한 angular.js와 같은 클라이언트 프레임워크에서도 활용할 수 있을 것이다.

안수찬 @dobestan

https://ansuchan.com/

서울대학교에서 컴퓨터공학을 전공하고, 오랜 기간 서비스 기획 및 개발을 해 왔습니다. 이러한 전문성을 인정받아 미래부 소프트웨어 마에스트로에 선정된 바 있습니다. 현재는 모바일 방송국, 퍼스트캔버스에서 컨텐츠로 새로운 가치를 그리고 있습니다. 나는 안수찬이다. 그러므로 나는 할 수 있다. me@ansuchan.com

View Comments...