Cookie란 무엇인가? SameSite 이슈 해결

HyunKyu Lee·2023년 11월 24일
0

42gg

목록 보기
3/4

문제상황

  • 42GG 로그인 기능 구현 도중 로컬에서는 잘 세팅되고 동작하던 Cookie가 test서버에 배포했을 때 세팅이 안되는 문제 발생
  • JWT기반의 로그인 기능에서 refresh token을 cookie에 set하는 과정에서 문제 발생

처음에 cookie는 다음과 받은 방법으로 브라우저에 세팅하고 있었다.

public static void addCookie(HttpServletResponse response, String name, String value, int maxAge, String domain) {
        ResponseCookie cookie = ResponseCookie.from(name, value)
                .maxAge(maxAge)
                .domain(domain)
                .path("/")
                .build();

        response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
    }
  • Cookie가 테스트 서버에 배포되고 난 뒤에는 addCookie()함수 자체가 제대로 동작하지 않아 로그인시에 Whitelabel Error Page 가 자꾸 나타났다.
  • 여기서 cookie설정에 대한 잘못됨을 깨닫고 RFC문서를 통해 Cookie에 대해 자세하게 공부하기로 했다.

Cookie란 무엇인가?

HTTP 쿠키는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각이다. 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이터를 함께 전송한다. 쿠키는 두 요청이 동일한 브라우저에서 들어왔는지 아닌지를 판단할 때 주로 사용합니다. 이를 이용하면 사용자의 로그인 상태를 유지할 수 있다. 상태가 없는(stateless) HTTP 프로토콜에서 상태 정보를 기억시켜주기 때문이다.

쿠키는 주로 세 가지 목적을 위해 사용된다:

  1. 세션 관리(Session management)
    서버에 저장해야 할 로그인, 장바구니, 게임 스코어 등의 정보 관리

  2. 개인화(Personalization)
    사용자 선호, 테마 등의 세팅

  3. 트래킹(Tracking)
    사용자 행동을 기록하고 분석하는 용도

| 여기서 주의해야할 점은 Cookie는 브라우저에 저장되기 때문에 민감한 정보를 보관하면 안된다


어떻게 보면 단순하게 Cookie를 브라우저에 set하고 브라우저는 해당 서버에 요청을 보낼 때마다 Cookie를 보내주는 것으로 간단해 보이지만 보안적인 측면이 적용되면 복잡해진다. 쿠키에 다음과 같은 속성을 알아보자

Domain

Domain이라는 속성은 쿠키를 수신할 수 있는 서버를 지정한다.
Domain을 지정하면 해당 서버와 해당 하위 도메인에서 쿠키를 사용할 수 있다. 예를 들어 Domain=mozilla.org로 설정하면 mozilla.org와 그 하위 도메인(예: developer.mozilla.org)에서 쿠키를 사용할 수 있다.
만약 쿠키를 사용하는 서버가 mail.naver.com, naver.com, shopping.naver.com 등 여러개라면 Domain 속성을 활용하여 여러 서버에서 Cookie를 공유하고 로그인 세션을 유지할 수 있다.

퍼스트 파티 쿠키와 서드 파티 쿠키

퍼스트 파트 쿠키와 서드 파티 쿠키는 다음과 같은 예시로 설명 가능하다.
만약 naver.com 에 접속하였을때 naver 사이트는 이미지를 가져오기 위해 다른 example.com 이라는 사이트에 이미지를 가져오는 요청을 할 수도 있다.

이때 사용자가 example.com에 대한 쿠키를 가지고있다면 이 쿠키가 전송되고 이를 서드 파티 쿠키라고 한다. 여기서 퍼스트 파티 쿠키는 naver.com의 쿠키로 사용자가 접속한 사이트의 쿠키를 의미한다. 만약 사용자가 example.com에 접속했다면 example.com에 설정된 쿠키는 naver.com에 접속했을 때 서드 파티 쿠키지만 지금은 퍼스트 파티 쿠기가 된다.

Path

Cookie의 Path 속성을 이용하면 서버의 특정 URL에 대해서만 쿠키를 전송할 수 있다 path 속성은 디렉터리 단위로 지정 가능하다.
예를 들어 Path=/docs를 설정하면 다음 경로에 쿠키를 전송한다.

  • /docs
  • /docs/
  • /docs/Web/
  • /docs/Web/HTTP

SameSite

쿠키에 별도로 설정을 가하지 않는다면, 브라우저들은 모든 HTTP 요청에 대해서 쿠키를 전송한다. 이는 CSRF 취약점을 만들어낸다. CSRF 공격은 다음과 같은 방식이다.

  1. 특정 사이트에 사용자가 로그인하여 해당 사용자 브라우저에 쿠키가 있는 상태
  2. 공격자는 해당 사용자에게 그럴듯한 사이트 링크를 누르도록 유도한다 (쿠키를 세팅해준 서버와 다른 도메인이지만 브라우저는 쿠키를 그냥 보낸다.)
  3. 링크를 누르면 HTTP문서가 열리는데 이를 통해 쿠키를 세팅해준 origin site에 http 요청을 보낸다.
  4. 이 요청에는 Cookie가 포함되어 있으므로 공격자가 아닌 로그인 된 사용자가 요청을 보내는 것 처럼 동작한다.

SameSite속성을 사용하면 서버가 쿠키를 cross-site간에 전송할지 말지 결정할 수 있다. 이를 통해CSRF 공격을 어느 정도 방지할 수 있다. 속성 종류로는 None, Lax, Strict세 가지 종류를 선택할 수 있다

  • None : SameSite가 탄생하기 전 쿠키와 동작하는 방식이 같다. corss site인 경우에도 cookie를 항상 전송한다.
  • Strict : 가장 보수적인 정책, cross site 요청에는 항상 전송되지 않는다. 심지어 서드 파티 쿠키조차 전송하지 않는다.
  • Lax : Strict와 비슷하지만 좀 더 느슨, Lax로 설정된 경우 대체로 서드파티 쿠키는 전송되지 않지만 예외적인 요청에는 전송된다.

Lax 서드 파티 쿠키가 전송되는 경우
같은 웹 사이트일때는 당연히 전송되고, 이외에는 Top Level Navigation(웹 페이지 이동)과, "안전한" HTTP 메서드 요청에 경우 전송된다.
Top Level Navigation에는 유저가 링크 를 클릭하거나, window.location.replace 등으로 인해 자동으로 이뤄지는 이동, 302 리다이렉트를 이용한 이동이 포함된다. 하지만 iframe이나 img를 문서에 삽입함으로서 발생하는 HTTP 요청은 "Navigation"이라고 할 수 없으니 Lax 쿠키가 전송되지 않고, iframe 안에서 페이지를 이동하는 경우는 "Top Level"이라고 할 수 없으므로 Lax 쿠키는 전송되지 않는다.

또한 "안전하지 않은" POST나 DELETE 같은 요청의 경우, Lax 쿠키는 전송되지 않는다. 하지만 GET처럼 서버의 서버의 상태를 바꾸지 않을 거라고 기대되는 요청에는 Lax 쿠키가 전송됨.

secure

secure이 적용된 쿠키는 HTTPS 요청에만 전송되는 쿠키이다. chrome에서는 secure속성이 적용되어있지 않은 쿠키의 SameSite=None 속성을 허용하지 않는다.

httpOnly

httpOnly 속성을 사용하면 js로 cookie에 접근하는 것을 예방할 수 있다.


프로젝트 적용

  • Cookie에관해 공부하면서 문제는 corss domain 관련 문제였다는 것을 알게 되었다.
  • client server (dev.42gg.kr), backend server (dev.api.42gg.shop) 로 도메인이 서로 달랐기 때문에 Cookie가 제대로 설정되지 않았다.
  • 우리 서비스는 JWT token을 Authorization헤더에 넣어서 클라이언트에서 서버로 보내고 있기 때문에 앞서 이야기한 CSRF취약점이 없다. 따라서 SameSite=None 옵션을 통해 문제를 해결하고자 했지만 좋은 방법은 아닌 것 같아서 도메인 유지 비용도 아낄겸 42gg.kr 서브도메인에 백엔드 서버를 옮기기로 했다.
 public void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
        String domain = applicationYmlRead.getDomain();
        ResponseCookie cookie = ResponseCookie.from(name, value)
                .maxAge(maxAge)
                .domain(domain)
                .httpOnly(false)
                .path("/")
                .secure(true)
                .build();
        response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
    }
  • 다음과 같이 쿠키를 설정해주었고 httpOnly를 설정하지 않은 이유는 클라이언트 서버에서 쿠키에서 refresh token에 접근하여 access token발급 요청을 보내야하기 때문이다.
참고
profile
backend developer

0개의 댓글