Django

Django - simple jwt 적용 (1) XSS, CSRF

김지훈_ 2022. 3. 16. 16:16

회사에서 사용하는 기존의 회원 시스템에는 큰 문제가 있었다.

 

django 에서 제공하는 'rest_framework.authtoken' 을 사용하여 Token 방식으로 인증과 인가를 하고 있었는데, 문제는 이 토큰이 갱신되지 않는다는 것이었다.

 

현시점 기준으로 서비스가 그렇게 크지 않아 보안 이슈가 생긴 적은 없으나, 해당 문제가 기술 부채로 남을것 같아 이번 기회에 simple jwt 를 적용하여 문제를 해결하기로 했다.

 

jwt 를 도입하며 가장 고민한 부분은 XSS공격과 CSRF공격 이었다. 여러 기술 블로그들을 검색해본 결과 많은 개발자들이 비슷한 고민을 하고 있는듯 했다.

 

jwt에 대한 얘기를 풀기전에 XSS 와 CSRF 를 간단하게 알아보면

 

XSS(Cross Site Scripting): 사용자가 악의적인 스크립트를 실행하여 사용자의 정보를 탈취하는 공격 즉 사용자를 대상으로한 공격이다.

XSS는 웹사이트에 악의적인 스크립트를 넣어놓고 사용자가 이 스크립트를 읽게끔 유도하여 유저의 정보를 빼오는 공격 기법이다.

사용자가 해당 스크립트를 읽으면 쿠키 또는 로컬 스토리지 값을 해커의 서버로 전송할 수 있게되고, JWT 역시 탈취될 수 있다.

 

 

CSRF(Cross-site request forgery): 사용자 의지와는 상관없이 해커가 의도한 행위(수정, 삭제, 등록 등)를 사용자 권한을 이용해 서버에 요청을 보내는 공격을 의미한다.

 

예를 들어 사용자가 A사이트에 로그인하여 쿠키를 가지고 있을 때, 해커가 사용자에게 악의적인 사이트에 들어오도록 유도한다. 악의적인 사이트에는 A사이트에 생성, 수정, 삭제등의 요청을 보내는 스크립트가 들어있고, 사용자는 의도치 않게 (생성, 수정, 삭제) 요청을 보내게된다.

 

정리하면, 쿠키나 로컬스토리지에 토큰을 저장하는 것 둘 다 공격을 당할 수 있다는 것

 

그럼 어떻게 하는 것이 최선일까?

 

결론부터 말하자면, acess token은 로컬 변수로 담고 refresh token은 쿠키에 담는 것이 최선인 것 같다. 동시에 DB에도 refresh token 을 저장하여, 쿠키의 refresh token이 혹시나 탈취당한 것이 확인되면 DB의 해당 refresh token 을 만료시켜 더 이상 사용하지 못하게 하는 것이다.

 

로컬 변수에 담으면 쿠키나 로컬 스토리지에 저장되지 않기 때문에, 탈취 당할 일이 API를 호출하는 시점을 제외하고는 없어진다.

쿠키 역시 다음과 같이 여러가지 보안 옵션을 제공한다.

Csrf Token
임의의 난수(Csrf Token)를 생성해 서버 메모리(세션)에 저장하고 클라이언트에 전달한다.
클라이언트는 중요한 요청(생성, 삭제, 수정)을 보낼 때 파라미터로 Csrf Token을 같이 보내 검증을 한다. 이렇게 했을 경우 CSRF 공격을 당해도 CSRF Token은 서버에 전달되지 않으므로 서버는 요청을 수행하지 않게 된다.
Cookie Referer Check
요청을 보내면 요청을 보낸 Domain을 알 수 있는데 이 Domain이 내가 허용한 Domain에서 온 요청인지 체크하면 된다. 일반적으로 Referer Check로만 대부분의 CSRF를 방어할 수 있다.
Cookie SameSite
Cookie의 SameSite 속성은 외부 사이트에 쿠키 전송할 범위를 설정할 수 있다.
속성은 총 3가지가 있다.

1. Strict : Cookie를 전달 할 때 현재 페이지 도메인과 요청받는 도메인이 같아야만 쿠키가 전송

2. Lax : Strict에서 <a href>, <link href>, GET Method 요청을 제외하고 Strict랑 같음(크롬 80버전부터는 SameSite Default값이 Lax로 설정된다)

3. None : 도메인 검증 안함 (대신 secure 옵션이 필수로 붙어야함) SameSite를 사용하기 위해서는 프론트와 백엔드의 도메인을 맞추거나 Nginx 프록시를 사용해서 요청을 해야할 것이다.

 

API를 호출할 때는 acess token 을 사용하는 대신 토큰의 만료 시간을 짧게 주어 혹시나 탈취 되더라도 오래 사용하지 못하게 하는 것이 첫번째이고

acess token 이 만료되어 갱신을 위해 refresh token 을 사용할 때, 해당 토큰이 탈취 되면 DB에서 삭제시키거나 만료시켜 사용하지 못하도록 하는 것이 두번째이다.

 

다음 포스트에서는 서버에 jwt를 어떻게 적용했는지 알아보겠다.