본문 바로가기

computer science

Cookie & Storage, 뭘 사용해야 할까?

cookie, storage, session, session cookie, web storage, local storage.. 웹 클라이언트의 저장 공간을 이야기할 때 자주 혼용해서 사용하는 단어들이다. 최근에 지인과 cookie 관련된 내용을 이야기하였는데 알고 있다고 생각했지만 설명하려고 하니 잘 알고 있지 못하고 있다는 것을 깨달아 다시 한 번 정리해보기로 했다.

웹 클라이언트에서 데이터를 저장하기 위해 사용하고 있는 공간은 크게 Cookie와 Web Storage 2개로 나뉜다. 만료 시점에 따라서 각 저장 공간은 다시 2가지로 나뉜다. Cookie는 session cookies/persistent cookies로 나뉘고, Web Storage는 session storage/local storage로 나뉜다.

session cookies는 브라우저가 종료되면 삭제되고, persistent cookies는 지정된 만료일에 삭제된다. session storage는 세션 종료 시 삭제되고, local storage는 반 영구적으로 사용된다.

그러면 cookie와 storage는 언제 사용하는 것이 좋을까? 내용은 JWT tokens를 어디에 저장해야 하는가에 대한 설명이지만, cookie와 storage를 언제 사용해야 하는지를 이해하기 쉬운 글이라고 생각되어 가져왔다.(의식의 흐름이 재미있기도 하다) 다음 글은 해당 내용을 번역한 글이다.

https://dev.to/gkoniaris/how-to-securely-store-jwt-tokens-51cf

 

How to securely store JWT tokens.

Article originally published here. In the last years, JWT tokens are widely used as an authenticatio...

dev.to

(JWT에 대한 설명은 생략하였습니다)

JWT를 local storage에 저장해야 할까?

많은 사람들이 JWT를 웹 브라우저의 local storage에 저장한다. 이러한 전략은 XSS라 불리는 공격에 취약하다. local storage는 같은 도메인에서 실행되는 모든 자바스크립트 코드가 접근할 수 있기 때문이다. 따라서 답은 'No, 절대 local storage에 JWT를 저장하지 말라'이다.

그러면 session storage는 어떨까?

session storage도 local storage와 마찬가지로 같은 도메인의 자바스크립트 코드가 접근할 수 있다. 한 가지 유일하게 달라진 점은 브라우저가 닫히면 JWT가 사라지고 사용자는 다시 로그인해야 한다는 것이다. 따라서 이번 답도 'No, 절대 session storage에 JWT를 저장하지 말라'이다.

항상 browser memory를 사용한다면?

토큰을 우리 코드에서만 접근 가능하게 하는 것은 안전한 해결책이다. 주의할 것은 브라우저를 새로고침하면 사용자는 다시 로그인을 해야 한다는 것이다. Not so cool right? 어떤 이유일지는 모르겠지만 사용자가 브라우저를 새로고침할 때마다 로그인을 해야 하는 상황이라면 추천하겠다.

쿠키 사용

위의 방법들을 모두 사용할 수 없다면 뭘 사용해야 할까? 쿠키가 오래된 방법이라고는 하나 여전히 훌륭한 방법이다. 쿠키는 browser request에 포함된다(원한다면 ajax에도 포함될 수 있음).

쿠키도 자바스크립트를 통해 접근가능하지 않나?

맞는데, server가 HttpOnly flag(authentication or authorization cookies에 항상 설정하는)를 설정하지 않는 경우에 한해서만 맞다. 그러면 된건가?

물론 아니다. 누군가 non-secure HTTP request를 통해 보낸다면 어떻게 될까?

요즘엔 누구도 plain HTTP를 사용하지 않지만, 이렇게 해도 쿠키를 만들 때 Secure flag를 설정할 수 있어서 절대 non-secure connection을 통해 쿠키가 보내지지 않는다.

CSRF를 잊은 것 같은데?

CSRF 공격은 브라우저의 기본 동작을 악용해 모든 쿠키를 cross-domain 요청에서도 전송하게 하는 방식으로 수행된다. 제대로 처리하지 않는 경우 큰 보안 취약점이 생긴다. 예로는 다음과 같은 것이 있다.

공격자는 아름다운 제안을 담은 이메일을 보내고 '50% 할인 받기'라는 버튼을 추가해놓는다. 사용자가 이 버튼을 클릭하면 사용자의 비밀번호를 새로운 비밀번호로 변경하는 web application의 endpoint로 POST form을 제출한다. 쿠키는 모든 요청(cross-domain 요청도 포함해서)에서 전송되므로, 사용자가 이전에 로그인한 상태라면 endpoint는 정상적으로 작동한다. 이제 사용자는 logout되고 더 이상 로그인 할 수 없다.

이 공격은 단순히 공격자가 테스트 목적 정도로 수행할 수 있는 예에 불과하지만, 더 심각한 경우 사용자가 더 비싼 플랜으로 업그레이드하게 하거나 은행 계좌에서 공격자의 계좌로 돈을 이체하게 할 수도 있다.

그러면 이제 어떻게 해야하지?

서버는 모든 요청(일반적으로 POST, PUT, DELETE)마다 백엔드에서 생성된 토큰을 사용할 수 있다. 사용자가 요청 수행 시, 캐시 또는 심지어 DB에서 직접 토큰을 가져와 유효한지 확인할 수 있다. 그런데 이는 사용자가 새로운 페이지로 이동할 때마다 새로운 토큰을 생성해야 한다는 것을 의미한다. 대안은 무엇일까?

여러 해 동안 불행히도 쿠키 기반 인증을 사용할 때 안전을 보장하는 유일한 방법은 CSRF 토큰을 사용하는 것이었다. 2016년 부터 모던 브라우저는 SameSite라는 쿠키 정책을 구현하기 시작했다.

SameSite 쿠키 정책

SameSite 정책은 세 가지 값(None, Lax, Strict)를 가질 수 있다. None은 브라우저가 오랜 시간 쿠키를 처리해온 방식으로, 모든 가능한 요청(cross-domain 포함)에서 쿠키가 전송되는 것을 허용한다. modern browser에서 SameSite의 default value는 Lax다(다 그런 것은 아니다). Lax는 GET 요청은 cross-domain에서도 쿠키 전송할 수 있도록 요청하고, 다른 모든 요청은 쿠키를 포함하지 않는다. 이렇게 하면 사용자가 이메일에서 앱의 대시보드 화면으로 redirect 되는 경우와 같을 때는 쿠키를 사용할 수 있지만, 악의적인 form 데이터가 민감한 endpoint로 전송되는 것을 방지할 수 있다.

Lax는 모든 redirect GET 요청에서 작동한다는 점을 짚고 넘어가야 한다. ajax 요청에서는 쿠키가 전송되지 않으므로, 누구도 자신의 사이트에 코드를 추가해, 예를 들어 /me endpoint로 요청하여 로그인된 사용자의 개인 데이터를 훔칠 수 없다. redirect 요청의 경우에도 iframes에서는 무시된다.

cross-domain GET 요청에서 Lax 쿠키가 전송되는 가장 일반적인 방법은 다음과 같다.

  • 사용자가 링크를 클릭해 당신의 웹사이트로 redirect 되는 경우
  • 사용자가 GET 요청을 통해 당신의 웹사이트로 redirect 되는 경우(다른 사이트에서 폼을 사용해 쿼리 문자열에 동적 인수를 전달하려는 경우 유용함)
  • window.location.href="yoursite..."와 같은 명령을 사용하는 경우

Strict SameSite 정책은 어떤 방식으로도 cross-domain 요청을 통해 쿠키가 전달되는 것을 허용하지 않는다. 이는 일부 edge case에서는 유용할 수 있지만, 이 정책을 사용할 경우 간단한 link redirect도 사용자가 웹 애플리케이션에서 logout 상태로 남게 된다는 것을 알아야 한다. 물론 이것이 가장 안전한 방법이긴 하지만 말이다.

그러면 Lax, Strict를 사용하면 CSRF나 XSS 공격에도 잠 잘 오나?

거의 그렇다. 이 기능을 지원하는 최신 브라우저를 사용하고 있는지 확인해야 한다. 지원하지 않는 경우 당신의 application이 해당 브라우저 버전에서 현재 지원되지 않으며, 최신 버전으로 업그레이드 해야 한다는 메시지를 사용자에게 알리는 것이 가장 좋다. 이와 같은 메시지를 사용하지 않으면, 서버가 SameSite 정책을 설정하려고 해도 브라우저가 이를 무시할 가능성이 있다. 이전 브라우저에 대한 명시적인 지원이 필요하다면 CSRF 토큰 구현으로 대체해야 한다.