Home HTTP Cookie 기초
Post
Cancel

HTTP Cookie 기초

브라우저 쿠키, 웹 쿠키, HTTP 쿠키, 등으로 불리는 ‘쿠키’ (Cookie)는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각이다. 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이터를 함께 전송한다. 쿠키는 두개의 요청이 같은 브라우저로부터 왔는지 판단할 때 사용된다. 이를 이용하여 사용자의 로그인 상태를 유지할 수 있다. Stateless HTTP 프로토콜에서 상태 정보를 기억시켜주기 때문이다.

(Stateless : 컴퓨팅에서 무상태 프로토콜(stateless protocol)은 어떠한 이전 요청과도 무관한 각각의 요청을 독립적인 트랜잭션으로 취급하는 통신 프로토콜로, 통신이 독립적인 쌍의 요청과 응답을 이룰 수 있게 하는 방식이다. 무상태 프로토콜은 서버가 복수의 요청 시간대에 각각의 통신 파트너에 대한 세션 정보나 상태 보관을 요구하지 않는다)

Cookie는 주로 다음의 3가지 목적을 위해 사용된다

  • 세션 관리 (Session Management = 로그인 정보, 등)
  • 개인화 (Personalization = 웹 페이지 테마, 등)
  • 트래킹 (Tracking = 사용자 사용 기록 저장, 등)

각 브라우저들마다 Cookie의 최대 개수 및 최대 크기를 따로 정의하는데 해당 정의는 해마다 조금식 바뀌는 것으로 보인다. 참고 사이트에 의하면 다음과 같이 브라우저 별 Cookie의 최대 개수 및 크기를 정의한다고 한다.

Untitled

쿠키를 만들 때 다양한 원소들이 필요로하는데, 그중 일부는 다음과 같다.

  • Cookie의 이름 (쿠키 식별자)
  • Cookie의 값
  • Cookie의 유효기간, 브라우저가 얼마나 해당 쿠키를 브라우저에 들고 있을 지 결정하는 시간 (exipration date)
  • Cookie의 경로, 쿠키가 어떤 URL 경로에서 유효한지(사용할 수 있는지)를 명시해주는 설정 값이다. 이 경로 외에서는 유효하지 않으며 사용할 수 없다.
  • Cookie의 도메인, 어떤 서버 및 도메인에서 해당 쿠키를 사용할 수 있는 지 명시 해준다. 해당 서버 및 도메인 외에서는 사용할 수 없다.
  • Secure Connection을 사용할지에 대한 여부. SSL 같은 안전한 암호화 통신에서만 쿠키를 사용할 수 있음을 나타내는 설정 값
  • HTTPOnly를 사용할지에 대한 여부. 해당 설정이 걸려 있으면 JavaScript를 통해서 Cookie에 접근 할 수 없다.
  • Same-Site 설정. Lax, Strict, None의 설정이 있으며 일부 브라우저에서는 Same-Site가 걸려 있지 않으면 Default로 Lax 혹은 None을 사용하게 되어 있다.

Untitled

  • 추후에 크롬 Cookie DB의 Column 명을 통해서 더 많은 내용이 있음을 확인할 수 있다.

쿠키의 종류

in-memory cookie, transient cookie, non-persistent cookie 혹은 temporary cookie 라고도 불리며, 메모리에만 존재하는 쿠키로 쿠키를 만든 서버에서 만료시간이 존재하며 세션 쿠키를 발급받은 세션 (Ex: 브라우저) 종료 시 해당 쿠키를 삭제한다.

어떤 브라우저들은 재시작할 때 세션을 복원해 세션 쿠키가 무기한으로 존재할 수 있다.

해당 쿠키는 사용자의 단말기(브라우저)에 저장되지 않고 Cookie를 발급해준 서버 측에서 저장된다. (Stateful하다)

File Cookie, 한국어로는 영속 쿠키라고 불리며 해당 쿠키는 Expires 속성에 명시된 날짜에 삭제되거나, Max-Age 속성에 명시된 기간 이후에 삭제되는 쿠키를 말한다.

1
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;

영속 쿠키는 사용자의 단말기에 저장된다.

  • Question : 브라우저에서 Session 쿠키와 Persistent 쿠키를 구분하는 방법이 있을까?

    ㄴ 브라우저에서 Session 쿠키와 Persistent 쿠키를 구분하는 방법은 Expires 속성의 여부에 따라 판단한다고 한다. Session Cookie는 Expires 속성을 가질 수 없다.

  • Question : 쿠키는 File 및 브라우저에 저장된다면 어디에 저장되는지? (구체적으로), 메모리에 저장된다면 메모리 어디?

    ㄴ 검색한 바로는 Temporary Memory에 저장된다고 한다. 그러나 이게, cache, stack, heap, data, 등의 영역인지 인지하지 못하는 또다른 영역인지 잘 모르겠다. - 추후 분석으로 좀 더 명확한 위치를 알아야 할 것으로 보인다.

    ㄴ Persistent Cookie의 경우 Local 컴퓨터에 저장되게 되는데, 이를 확인할려면 다음과 같은 경로에 접근해야한다. ~/Library/Application Support/Google/Chrome/Default 해당 디렉토리에서 ls 명령어를 실행해보면 Local Storage, Session Storage 등 다양한 저장공간도 확인할 수 있다.

    Untitled

    또한 files 명령어를 통해 Cookies를 확인해보면 SQLite 형태의 파일로 저장된 것을 확인할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
      $ file Cookies
      Cookies: SQLite 3.x database, last written using SQLite version 3038005, file counter 96881, database pages 365, 1st free page 11, free pages 204, cookie 0x11, schema 4, UTF-8, version-valid-for 96881
        
      sqlite> .tables
      cookies  meta
        
      sqlite> PRAGMA table_info(cookies);
      0|creation_utc|INTEGER|1||0
      1|host_key|TEXT|1||0
      2|top_frame_site_key|TEXT|1||0
      3|name|TEXT|1||0
      4|value|TEXT|1||0
      5|encrypted_value|BLOB|1||0
      6|path|TEXT|1||0
      7|expires_utc|INTEGER|1||0
      8|is_secure|INTEGER|1||0
      9|is_httponly|INTEGER|1||0
      10|last_access_utc|INTEGER|1||0
      11|has_expires|INTEGER|1||0
      12|is_persistent|INTEGER|1||0
      13|priority|INTEGER|1||0
      14|samesite|INTEGER|1||0
      15|source_scheme|INTEGER|1||0
      16|source_port|INTEGER|1||0
      17|is_same_party|INTEGER|1||0
      18|last_update_utc|INTEGER|1||0
        
      sqlite> select * from cookies where host_key = 'localhost';
      13301250676749963|localhost||Idea-d20d715||xxxxx|/|13616610676749963|0|1|13303134028398267|1|1|1|2|1|63342|0|13301250676749969
      13303134028833694|localhost||JSESSIONID||xxxxx|/|0|0|1|13303134028833694|0|0|1|-1|1|80|0|13303134028833698
    

위 Cookie Table의 Column 명을 확인해보면, is_persistenthas_expires를 통해서 Session Cookie인지, Persistent Cookie인지 판별하는게 아닌가 싶다. (그러나 Session 쿠키는 브라우저의 메모리상에만 존재한다고 했는데, 왜 존재하는거지? - 정확한 이유는 아직 모르겠다)

Untitled

그 이유는 브라우저를 껐다 키지 않아서 였다. 해당 브라우저를 종료 시키고 재시작시 Session이 사라진것을 확인할 수 있었다.

Untitled

1
2
sqlite> select * from cookies where host_key = 'localhost';
13301250676749963|localhost||Idea-d20d715||xxxxxx|/|13616610676749963|0|1|13303751015084115|1|1|1|2|1|63342|0|13301250676749969
  • !!! 위 기준은 Chrome 브라우저 기준이며, Safari, Firefox는 조금 다를 수 있다. 그리고 SQLite DB에 있는 쿠키의 Value들은 전부 암호화 되어 있다. (양방향 암호화)
  • FireFox 경로 : ~/Library/Application Support/Firefox/Profiles/niftefmu.default-release 에서 cookies.sqlite 파일
    • 참고로 FireFox 쿠키 SQLite DB에는 Session 쿠키가 저장되지 않는다
  • Safari 경로 /Users/dyrandy/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies
    • 사파리 또한 FireFox와 동일하게 세션은 저장되지 않는다.
    • 크롬과 파이어폭스와 달리 SQLite DB 파일이 아닌 Binary 파일이다.
    • 해당 Binary 파일을 풀기 위해 pip 모듈을 사용하면 된다. (참고 : https://pypi.org/project/binarycookiesreader/)

Experiment#1 (Expire 설정)

정말로 Expire가 설정되지 않았으면 브라우저가 Session으로 인식할까 테스트해보고자 한다. 먼저 다음과 같은 코드가 있다고 가정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 환경은 NodeJS
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
    res.cookie("test_cookie", "dyrandy");
    res.send('Hello World!');
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

이제 localhost:3000에 접속하여 Cookie의 설정 상태를 확인해보면 놀랍게도 다음 이미지와 같이 Session으로 할당된 것을 확인 할 수 있다.

Untitled

1
2
sqlite> select * from cookies where host_key = '127.0.0.1';
13304010203325805|127.0.0.1||test_cookie||xxxxxxxxxx|/|0|0|0|13304010605994796|0|0|1|-1|1|3000|0|13304010605994799

이제 해당 쿠키의 Expire/Max-Age를 설정해보면 어떨까? 다음 코드와 같이 Expire를 추가해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
    res.cookie("test_cookie", "dyrandy", { expires: new Date(Date.now() + 900000 )});
    res.send('Hello World!');
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

Untitled

1
2
sqlite> select * from cookies where host_key = '127.0.0.1';
13304010203325805|127.0.0.1||test_cookie||xxxxxxxxxxxxx|/|13304011309985761|0|0|13304010409985761|1|1|1|-1|1|3000|0|13304010409985784

쿠키 설정

Domain 및 Path

Domain : 쿠키가 전송되게 될 Host를 명시한다. 이때 호스트가 명시되지 않는다면, Subdomain이 포함되지 않는, 현재 문서 위치의 호스트 일부를 기본값으로 설정한다. 만약 도메인을 명시한다면, Subdomain들은 항상 포함된다. 예를 들면 Domain=test.com이 설정되면, 쿠키들은 sub.test.com과 같은 subdomain 상에 포함된다.

Path : 쿠키 헤더를 전송하기 위하여 요청되는 URL 내에 반드시 존재해야하는 URL 경로다.

  • 참고로 쿠키는 Port Specific하지 않다. 즉, localhost:8080에서 특정 쿠키를 만들고 사용한다면, localhost:8000에서도 이를 사용할 수 있다. 만약 SetCookie에서 특정 Port를 지정한다면 구분이 가능하다 (위 쿠키 DB의 Column중 16번 Column에 source_port 존재)

Secure Flag

Secure 설정이된 쿠키는 HTTPS 프로토콜 상에서 암호화된 요청일 경우에만 전송된다.

HTTPOnly Flag

HTTPOnly 설정이된 쿠키는 JavaScript의 Document.Cookie API에 의해 접근할 수 없다. 서버 쪽에서 저장된 세션 쿠키는 JavaScript를 사용할 필요성이 없기 때문에 HttpOnly Flag가 설정되도 무방하다.

1
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

SameSite

SameSite 쿠키는 쿠키가 Cross-Site 요청과 함께 전송되지 않음을 설정할 수 있다. 이를 통해 CSRF 공격에 대해 보호 방법을 제공하고 있으며, SameSite는 여전히 실험단계에 있다고 한다.

SameSite에 들어가기 앞서 SameSite가 무엇을 의미하는지부터 파악해야한다. 먼저 요기서 “Site”란 Subdomain을 제외 한 최상위 도메인을 일컫는다. 즉, test.example.com에서 example.com을 말하는 것이고 test.example.com과 www.example.com 사이의 요청은 SameSite 요청이다.

그러나 *.github.io 같은 도메인은 서로 다른 “Site”로 본다, 즉, my.github.io와 your.github.io는 서로 다른 “Site”다. 그리고 같은 도메인이지만, http: 및 https: 처럼 다른 Scheme을 사용한다면 다른 사이트로 구분한다고 한다.

또한 SameSite를 들어가기 앞서 First-Party CookieThird-Party Cookie를 알아야한다. 만약 다음과 같은 test.com에 접속해본다고 가정해보자. 해당 test.com에서 example.com이 제공하는 이미지인 example.com/image.png를 사용하고 있다고 가정해보자. 이 경우 브라우저는 test.com에 접속해있으면서 example.com으로 요청을 보낸다.

1
2
3
4
5
6
7
8
9
<html>
  <head>
    <title>test.com</title>
    <meta property="url" content="http://test.com/" />
  </head>
  <body>
    <img src="http://example.com/image.png" />
  </body>
</html>

이 때 만약 test.com에 접속해있는 사용자가 example.com에 대한 쿠키를 가지고 있다면, image를 갖고오는 요청에서 해당 쿠키가 example.com으로 같이 전송된다. 이때 전송되는 example.com의 쿠키를 Third-Party Cookie라 하며, test.com의 쿠키는 First-Party Cookie라 부른다.

즉, Third-Party Cookie는 사용자가 접속한 페이지와 다른 도메인으로 전송하는 쿠키를 말하고, First-Party Cookie는 사용자가 접속한 페이지와 같은 도메인으로 전송되는 쿠키를 말한다.

이때 전송되는 Third-Party Cookie에 대하여 제어를 한 것이 바로 SameSite이다.

SameSite에는 3가지 설정이 존재한다. None, Strict, Lax 가 존재한다.

  • None : 해당 값으로 설정된 쿠키의 경우 Cross-Site Request의 경우 항상 전송된다. 즉, test.com에서 example.com의 이미지로 요청을 날릴때, 혹은 POST같은 요청을 보낼 때 쿠키는 항상 전송된다. SameSite=None 설정 시 Secure Flag가 필수로 적용되어야 한다고 한다. (크롬 기준)

    Untitled

  • Strict : 가장 강력한 정책으로 어떠한 Cross-Site Request이더라도 Third-Party Cookie는 전송되지 않는다.
  • Lax : Strict 설정과 유사하지만 Strict만큼 강한 정책이 아닌, 상대적으로 느슨한 정책이다. 예를 들면 다음과 같다.

    1
    2
    
      A cookie with "SameSite=Lax" will be sent with a same-site request, 
      or a cross-site top-level navigation with a "safe" HTTP method.
    

    즉, 위 문구를 번역해보자면, SameSite=Lax가 설정된 쿠키는 Same-Site 요청, Top Level Navigation(웹 페이지 이동)과, “안전한” HTTP Method의 경우에만 전송된다는 것이다.

    요기서 Top Level Navigation, 즉, 웹 페이지 이동은 <a> 태그에 있는 링크를 클릭하거나, window.location.replace 등으로 자동으로 이동되는 경우, 그리고 302 같은 Redirection을 말한다. <iframe>과 <img>를 삽입하여 발생하는 경우는 포함하지 않는다.

    그리고 “안전한” HTTP Method라 하면 GET Method를 일컫는다. POST와 DELETE같은 요청은 서버의 상태를 바꿀 가능성이 있기 때문에 전송되지 않는다고 한다.

!!! 참고로 크롬은 Lax를 기본설정으로 사용하고 있다. 즉, 예전에는 SameSite가 없었을 시 SameSite=None이 기본설정이었지만, 2020년 2월 4일 크롬 Version 80 배포에서 SameSite가 설정되지 않으면 Lax를 Default 값으로 지정했다.

Untitled

!!! FireFox의 경우 기본적으로 OFF이며, about:config에 접속 시 해당 설정을 바꿀 수 있다. (69버전 이후부터)

Untitled


Experiment#2 (Default Lax)

정말로 브라우저 별로 SameSite가 걸리지 않는다면 Lax로 바뀌는지 및 안바뀌는지 Chrome, Firefox, Safari로 실험 해보겠다.

먼저 다음과 같은 코드가 있다고 가정하자, Session Cookie가 만들어질 것이고, 해당 쿠키에는 SameSite 설정이 없다.

1
2
3
4
5
6
7
8
9
10
const app = require("express")()

app.get("/", (req, res) => {
    const cookie = "test=dyrandy; Max-Age=9000000;";

    res.setHeader("set-cookie", [cookie])
    res.send("Made Cookie")
})

app.listen(8000, ()=>console.log("listening on port 8000"))

Chrome

먼저 크롬에서 해당 사이트에 접속 해보았다. 쿠키 생성 도메인에 접속하여 SameSite 설정을 보았지만, 아무런 설정이 없는 것을 확인 할 수 있다.

Untitled

그리하여 SameSite가 적용이 안되나 싶었지만, SQLite 파일을 통해 쿠키의 설정을 확인해보았다.

1
2
sqlite> select * from cookies where host_key = 'xxx.xxx.xxx.xxx';
13304101943912455|xxx.xxx.xxx.xxx||test||dyrandy|/|0|0|0|13304102248051917|0|0|1|-1|1|8000|0|13304102248051921

뒤에서 5번째 Column이 SameSite 설정이므로 -1이 값이 된다.

1
2
3
4
test = "13304101943912455|xxx.xxx.xxx.xxx||test||dyrandy|/|0|0|0|13304102248051917|0|0|1|-1|1|8000|0|13304102248051921"
val = test.split('|')
print(val[14])
# -1
  • 참고로, 2 = Strict, 1 = Lax, 0 = None이다.
  • 애초에 SameSite=None인 쿠키가 Secure 옵션 없이 생성되지 않는다.

Firefox

파이어폭스도 크롬과 동일하게 쿠키 생성 도메인에 접속하여 SameSite 설정을 확인해보았다. 그림과 같이 SameSite=None이 적용된 것을 확인할 수 있다.

Untitled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# /Users/dyrandy/Library/Application Support/Firefox/Profiles/niftefmu.default-release/cookies.sqlite

sqlite> PRAGMA table_info(moz_cookies);
0|id|INTEGER|0||1
1|originAttributes|TEXT|1|''|0
2|name|TEXT|0||0
3|value|TEXT|0||0
4|host|TEXT|0||0
5|path|TEXT|0||0
6|expiry|INTEGER|0||0
7|lastAccessed|INTEGER|0||0
8|creationTime|INTEGER|0||0
9|isSecure|INTEGER|0||0
10|isHttpOnly|INTEGER|0||0
11|inBrowserElement|INTEGER|0|0|0
12|sameSite|INTEGER|0|0|0
13|rawSameSite|INTEGER|0|0|0
14|schemeMap|INTEGER|0|0|0

sqlite> select * from moz_cookies where host="xxx.xxx.xxx.xxx";
130301||test|dyrandy|xxx.xxx.xxx.xxx|/|1660529133|1659629133437204|1659629114551764|0|0|0|1|0|1

Python코드를 돌려보면

1
2
3
4
test = "130301||test|dyrandy|xxx.xxx.xxx.xxx|/|1660529133|1659629133437204|1659629114551764|0|0|0|1|0|1"
val = test.split('|')
print(val[13])
# 0

SameSite 설정은 맨뒤에서 3번째, 즉 0이 된다. (요기서 봐야할 것은 rawSameSite 설정이다)

그러면 Lax Cookie는 어떤 값일까?

1
2
3
4
5
6
7
sqlite> select * from moz_cookies where host="xxx.xxx.xxx.xxx";
130316||test|dyrandy|xxx.xxx.xxx.xxx|/|1660529672|1659629672510695|1659629114551764|0|0|0|1|1|1

test = "130301||test|dyrandy|xxx.xxx.xxx.xxx|/|1660529133|1659629133437204|1659629114551764|0|0|0|1|1|1"
val = test.split('|')
print(val[13])
# 1
  • !!! 참고 Strict는 2다

Safari

사파리의 경우는 아래와 같이 SameSite 설정이 없으면 아무것도 나오지 않는다. 그리하여 Cookie의 경로에 들어가 직접 Binary Cookie를 읽어보고자 했다.

Untitled

1
2
3
4
5
6
$ pip install binarycookiesreader

$ bcr -i Cookie.binarycookies

$ cat Cookies.txt | grep xxx.xxx.xxx.xxx
Cookie : test=dyrandy; domain=xxx.xxx.xxx.xxx; path=/; expires=Mon, 01 Jan 2001; Unknown

그러나 쿠키는 읽을 수 있으나, SameSite 설정에 대한 내용은 없다.

참고

This post is licensed under CC BY 4.0 by the author.

-

Oauth2.0는 무엇일까?