펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



쿠키와 세션

모두 91명의 회원님이 완료했어요.


이 훈련에서는 웹(Web)상에서 사용자를 식별하기 위한 인증과 사용자 상태를 추적하기 위해 활용되는 HTTP 쿠키와 세션에 대해 배울 수 있습니다.





쿠키(Cookie)


쿠키란 무엇인가?

위키백과에서는 쿠키에 대한 정의를 아래와 같이 서술하고 있습니다.

"쿠키란 하이퍼 텍스트의 기록서(HTTP)의 일종으로서 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 그 사이트가 사용하고 있는 서버를 통해 인터넷 사용자의 컴퓨터에 설치되는 작은 기록 정보 파일을 일컫는다. HTTP 쿠키, 웹 쿠키, 브라우저 쿠키라고도 한다. (이하 생략)"

위키백과의 정의와 같이 쿠키란 사용자가 웹사이트에 방문할 때 클라이언트측에 저장되는 key=value쌍의 문자열로 구성된 작은 임시 파일입니다. 웹 브라우저는 동일 서버에 재요청이 발생할 경우 저장된 쿠키 데이터를 Cookie 요청 헤더에 담아 함께 전송합니다.

쿠키는 HTTP 프로토콜의 특징인 비상태성(Stateless)으로 인해 사용자의 연속되는 요청들이 동일 사용자의 요청인지 식별하거나 웹사이트 내에서 사용자의 상태를 기억하기 위해 탄생했습니다. 쿠키는 온라인 쇼핑몰의 "최근 본 상품" 등 민감하지 않지만 서비스 제공에 필요한 데이터를 쿠키로 설정하여 사용하기도 하지만 사용자의 인증에도 사용되므로 공격자의 표적이 되기 쉽습니다.


쿠키는 무엇에 쓰는 물건인가?

쿠키를 사용하는 목적은 웹사이트마다 다릅니다만 일반적으로 사용자의 상태 데이터를 기억하여 아래와 같은 목적을 달성하기 위해 사용됩니다.

  • 세션 관리: 웹 사이트가 사용자를 식별하고 사용자의 로그인 정보를 기억할 수 있도록 합니다. 
  • 개인화: 사용자별 맞춤형 광고나 웹 사이트내 환경 설정 등과 같이 사용자의 웹사이트 환경을 개인화하기 위해 사용됩니다. 
  • 사용자 활동 추적: 사용자의 웹사이트 사용 패턴이나 검색 습관 등을 추적하고 분석하여 사용자가 선호할만한 다른 컨텐츠를 제안할 수 있도록 합니다.


쿠키는 어떤 유형들이 있을까?

쿠키의 라이프사이클(Lifecycle)의 관점에서 봤을 때 다음의 두 가지 유형이 있습니다.

  • 세션 쿠키(Session Cookie): 사용자가 웹 사이트를 방문하고 있는 동안 메모리에 저장되는 쿠키입니다. 임시 쿠키, 인메모리(In-Memory) 쿠키라고도 합니다. 사용자가 웹 브라우저를 닫을 때 삭제되며, 별도의 만료일자나 유효기간이 설정되지 않은 쿠키가 세션 쿠키로 간주됩니다.
  • 영구 쿠키(Persistent Cookie): 서버에 의해 만료 일시(Expires 속성)나 유효 기간(Max-Age 속성)이 설정된 쿠키이며 사용자의 하드디스크에 저장됩니다. 웹 브라우저가 닫힐 때 삭제되는 세션 쿠키와는 달리 설정된 만료 일시가 종료되거나 유효 기간이 종료되었을 때 삭제됩니다. 


쿠키는 어떻게 발급될까?

쿠키는 클라이언트의 HTTP 요청을 수신한 서버가 응답을 회신할 때 Set-Cookie 응답 헤더를 통해 발급됩니다.

Set-Cookie 응답 헤더의 기본적인 형식은 아래와 같습니다. 

Set-Cookie: Cookie-name=Some-Value;

또한, Set-Cookie 헤더는 다음의 속성들을 통해 웹 브라우저가 쿠키를 처리하는 방법을 제어할 수 있습니다.

각 속성과 속성별 예시입니다.

  • Expires: 쿠키의 유효기간을 설정합니다.  아래 예시는 2020년 10월 7일 05시 56분 24초까지 유효한 쿠키이며, 쿠키의 발급시간 기준으로 1200초 동안 서버로 전송됩니다.     
Set-Cookie: Cookie-name=Some-value; expires=Wed, 07-Oct-2020 05:56:24 GMT; Max-Age=1200;
  • Domain: 웹 브라우저에 의해 쿠키가 전송될 도메인을 결정합니다. 이 속성에는 쿠키를 발급하는 도메인의 최상위 도메인과 그 하위도메인에만 설정할 수 있습니다. 이 속성을 예시와 같이 bugbountyclub.com으로 설정하게 되면 bugbountyclub.com을 포함한 subdomain.bugbountyclub.com와 같은 모든 하위도메인에 대한 요청에도 쿠키를 전송하게 됩니다. 만일 이 속성이 지정되지 않는다면 요청된 리소스가 있는 도메인으로 간주되며 이 때는 하위도메인은 포함되지 않습니다. 
Set-Cookie: Cookie-name=Some-value; Domain=bugbountyclub.com;
  • Path: 웹 브라우저에 의해 쿠키가 전송될 URL 경로를 결정합니다. 아래 예시는 Domain 속성이 지정되지 않았으므로 위에서 설명한 바와 같이 요청된 리소스가 있는 도메인의 /bbc 경로 혹은 그 하위 경로로 요청할 때만 쿠키를 전송합니다. 가령, 요청된 리소스의 도메인이 www.bugbountyclub.com이라면 www.bugbountyclub.com/bbc나 그 하위 경로로 요청할 때만 쿠키가 전송됩니다.
Set-Cookie: Cookie-name=Some-value; Path=/bbc;
  • Secure: HTTPS를 통한 암호화된 연결인 경우에만 쿠키를 전송할 수 있으며, 별도의 값을 가지지 않습니다.
Set-Cookie: Cookie-name=Some-value; Secure;
  • HttpOnly:  클라이언트측에서 자바스크립트(document.cookie)를 통해 쿠키에 접근할 수 없게 하며, 별도의 값을 갖지 않습니다. XSS(Cross Site Scripting) 공격의 완화 수단으로 사용됩니다.
Set-Cookie: Cookie-name=Some-value; HttpOnly;
  • SameSite: 크로스 도메인을 통한 요청에 쿠키를 포함시킬지 여부를 결정합니다. 설정 가능한 값으로는 Lax, Strict, None이 있습니다. Lax는 크로스 도메인이라도 GET 요청인 경우 쿠키를 전송할 수 있게 합니다. Strict는 자기 자신에서 발생한 요청인 경우에만 쿠키를 전송할 수 있게 합니다. None은 모든 크로스 도메인 요청에서 쿠키를 전송할 수 있게 합니다. 이 속성은 종종 CSRF(Cross Site Request Forgery) 공격의 완화 수단으로 사용되기도 합니다. 
Set-Cookie: Cookie-name=Some-value; SameSite=Strict;


물론 다음과 같이 여러 개의 속성을 나열하여 함께 설정할 수도 있습니다.

Set-Cookie: Cookie-name=Some-value; expires=Wed, 07-Oct-2020 05:56:24 GMT; Max-Age=1200; Domain=bugbountyclub.com; Path=/bbc; Secure; HttpOnly; SameSite=Lax;


위에서 살펴본 Set-Cookie 응답 헤더를 통해 서버에 의해 발급된 쿠키는 클라이언트측에 저장(클라이언트측 어디에 저장될지는 서버가 쿠키를 어떻게 발급하느냐에 따라 다릅니다.)되었다가 이후 클라이언트가 요청을 할 때마다 저장된 쿠키값을 포함시켜 웹 브라우저에 의해 서버로 자동으로 전송되게 됩니다. 서버는 클라이언트로 부터 전송된 쿠키값을 통해 사용자를 식별하거나 사용자의 상태를 확인합니다.


그럼 쿠키가 발급되고 사용되는 과정을 그림을 통해 살펴봅시다.


foo 라는 계정을 가진 사용자가 https://www.shoppingmall.com 이라는 온라인 쇼핑몰에 방문하여 로그인을 하기 위해 아래의 POST 요청을 서버로 전송합니다.

POST /login HTTP/1.1
Host: www.shoppingmall.com
...생략...

username=foo&password=bar


요청을 받은 서버는 사용자 인증을 처리하고, Set-Cookie 응답 헤더에 쿠키값을 담아 클라이언트에게 보냅니다. 

HTTP/1.1 200 OK
Set-Cookie: username=foo;
...생략...

Welcome, foo! 

위 예시에 보여드린 쿠키는 Expires나 Max-Age 속성이 없이 발급되었으므로 현재 세션에만 유효한 세션 쿠키에 해당됩니다. 이 응답을 받은 foo 사용자의 웹 브라우저는 Set-Cookie 응답 헤더의 값을 메모리에 저장합니다.


이제 foo 사용자는 자신의 장바구니 목록을 확인하기 위해 아래의 GET 요청을 서버로 전송합니다. 이 때 웹 브라우저는 저장되어 있던 쿠키값을 Cookie 요청 헤더에 담아 서버로 전송합니다. 이 과정은 사용자의 개입없이 웹 브라우저가 자동으로 수행하게 됩니다. 

GET /cart HTTP/1.1
Host: www.shoppingmall.com
Cookie: username=foo;
...생략...


서버는 Cookie 요청 헤더의 값을 통해 수신 받은 요청이 foo 사용자의 요청이라 판단하고, foo 사용자의 장바구니 목록을 응답으로 전송합니다.

HTTP/1.1 200 OK
...생략...

foo's Cart List


만일 쿠키가 없다면 HTTP의 비연결성, 비상태성의 특징으로 인해 foo와 서버간의 연결은 유지되지 않고 종료되며, 서버는 이후 발생하는 foo 사용자의 요청이 foo로부터 받은 요청인지 알 수 없게 됩니다. 하지만 쿠키의 사용으로 인해 우리는 로그인 후 지속적으로 인증된 상태에서 웹 사이트를 사용할 수 있게 되는 것입니다. 물론 위와 같이 클라이언트측에만 저장되는 쿠키만을 사용해 사용자 인증을 구현한 웹사이트는 없습니다. 클라이언트측에 저장된다는 말은 바꿔 말해서 얼마든지 사용자에 의해 데이터 변조가 가능하다는 의미입니다. 위와 같이 구현된 웹사이트는 Burp suite과 같은 인터셉팅 프록시 도구를 사용하여 username=foo라는 쿠키값을 username=admin으로 변경하는 것만으로도 누구든지 관라자가 될 수 있으며, 다른 사용자의 계정명만 알고 있다면 다른 사용자로 위장할 수도 있습니다. 따라서 많은 웹사이트들이 저마다 다양하고 안전한 사용자 인증 방법을 선택하여 사용하고 있지만 일반적인 웹사이트의 경우 쿠키와 세션(Session)이라는 개념을 결합하여 세션 기반의 사용자 인증을 구현하게 됩니다. 


세션(Session)


앞서 살펴본 쿠키에는 임의의 사용자가 쿠키를 조작하여 다른 사용자 행세를 할 수 있다는 보안상 치명적인 문제점이 있었습니다. 세션은 이러한 쿠키의 문제점을 방지하기 위해 사용됩니다.  


세션이란 무엇인가?

세션은 일정 시간 동안 사용자와 웹 서버간의 연결을 유지해주는 기술입니다. 세션을 통해 웹사이트는 사용자 활동에 따른 상태 데이터를 기억할 수 있습니다. 세션은 상태 데이터를 저장하고 추적할 수 있다는 면에서 봤을 때 쿠키와 비슷하지만 쿠키는 클라이언트측에 저장되는 반면 세션은 서버측에 저장되고 관리된다는 큰 차이가 있습니다.


세션의 상태 관리

서버가 세션을 통해 상태 정보를 유지하기 위해서 사용할 수 있는 기술은 다음과 같습니다.

  • 쿠키(Cookie): 대부분의 웹사이트는 세션 토큰을 저장하기 위해 쿠키를 사용합니다. 앞서 살펴본 쿠키의 속성을 사용하게 되면 다른 방법에 비해 상대적으로 안전한 구현이 가능합니다. 서버는 클라이언트가 매 요청마다 Cookie 요청 헤더를 통해 전송하는 세션 토큰을 통해 세션 상태를 유지할 수 있습니다.
  • 숨겨진 Form(폼) 필드: 서버는 클라이언트에게 전송될 HTML 폼의 숨겨진 필드에 세션 토큰을 동적으로 포함시킵니다. 클라이언트가 다시 폼을 제출할 때 다시 서버로 전송됩니다.
  • URL 재작성(rewrite): 클라이언트로 부터 요청되는 URL에 고유한 세션 토큰을 동적으로 포함시킵니다. 웹사이트에 포함된 모든 a태그의 href나 form 태그의 action 속성에 들어갈 URL에 고유한 세션 토큰을 포함시켜야 합니다. 


세션은 어떻게 동작하는가?

쿠키를 활용한 세션 기반의 사용자 식별이 어떻게 처리되는지 살펴보겠습니다. 


 foo 라는 계정을 가진 사용자가 온라인 쇼핑몰에 로그인합니다.

POST /login HTTP/1.1
Host: www.shoppingmall.com
...생략...

username=foo&password=bar


요청을 받은 서버는 클라이언트로 부터 전달받은 세션 토큰이 없다면 세션 토큰을 생성하고, foo 사용자에 대한 세션 토큰은 a1b2c3임을 저장해둡니다. 이 세션 토큰은 Set-Cookie 응답 헤더에 포함되어 foo에게 전송됩니다. 사용자와 웹 서버간에 세션이 수립되었으며, 이렇게 로그인 인증을 통해 생성된 세션을 인증된 세션이라고도 합니다. 참고로, 로그인하지 않은 사용자와의 익명 세션이 수립되기도 합니다.  

HTTP/1.1 200 OK
Set-Cookie: sessionid=a1b2c3;
...생략...

Welcome, foo! 


foo는 자신의 장바구니 내역을 조회하기 위해 서버에게 발급받았던 세션 토큰과 함께 아래의 GET 요청을 서버로 전송합니다.

GET /cart HTTP/1.1
Host: www.shoppingmall.com
Cookie: sessionid=a1b2c3;

...생략...


서버는 요청에 포함된 세션 토큰의 값이 저장되어있던 foo의 세션 토큰과 일치하는지 확인 후 일치한다면 foo의 장바구니 목록을 보여줍니다.

HTTP/1.1 200 OK
...생략...

foo's Cart List


foo가 로그아웃을 하거나 서버가 설정해둔 일정 시간이 지나면 유지되고 있던 세션은 자동으로 종료됩니다.


세션은 안전할까?

세션은 사용자의 상태 데이터가 서버에 저장되므로 클라이언트측에 저장되는 쿠키에 비해 상대적으로 안전하다고 말할 수 있습니다. 하지만 세션 토큰이 공개적으로 노출되거나 예측이 가능할 수준으로 복잡하거나 길지 않다면 공격자는 노출된 토큰을 그대로 사용하거나 무차별 대입 등을 통해 웹사이트의 타겟 사용자나 현재 유효한 세션이 유지되고 있는 임의의 사용자로 위장할 수 있습니다. 



어떻게 해야 세션을 안전하게 관리할 수 있을까?

세션 토큰의 노출과 예측을 방지하기 위해서는 다음과 같이 세션 관리를 구현해야 합니다.


예측할 수 없는 세션 토큰

사용자에게 쿠키를 통해 발급되는 세션 토큰은 SHA256과 같은 암호화 해시를 통해 충분히 길고 암호화된 임의의 문자열로 구성되어야 합니다. 길이는 최소 128bits 이상이어야 하며, 충분한 무작위성이 보장되어야 합니다. 최소 64비트 이상의 엔트로피를 제공해야 하며, 세션 토큰은 사용자를 식별, 추적하므로 중복되지 않고 고유해야 합니다. 


암호화된 HTTPS 통신 사용

HTTP를 사용할 경우 암호화중간자 공격에 취약할 수 있습니다. 웹사이트 전반에 걸쳐 HTTPS를 사용해야 합니다. 


쿠키 보안 속성 설정

  • Domain & Path: 쿠키가 전송될 도메인과 경로를 최대한 제한적으로 설정해야 합니다.
  • Secure: HTTPS 연결인 경우에만 쿠키를 전송하도록 설정해야 합니다. HTTP 연결에서 쿠키를 전송할 경우 네트워크 트래픽을 감청하고 있는 공격자에게 세션 토큰이 노출될 수 있습니다. 
  • HttpOnly: 스크립트를 통해 쿠키에 접근할 수 없도록 설정해야 합니다.
  • Expires & Max-Age: 이 두 속성을 설정하지 않은 채로 세션 쿠키를 사용하도록 설정하는 것이 더 안전합니다. 하지만 불가피하게 만료 일시나 유효 기간을 설정해야 하는 경우 불필요하게 오랫동안 남아있지 않도록 최대한 방어적으로 설정해야 합니다. 
  • SameSite: 크로스 도메인간 쿠키를 전송할 수 없도록 설정해야 합니다.  


참고 문헌