본문 바로가기

Security Study/Web

[Dreamhack] Cookie & Session

Background: Cookie & Session

1. 쿠키

1.1. 쿠키

Key와 Value로 이뤄진 일종의 단위로, 서버가 클라이언트에게 쿠키를 발급하면, 클라이언트는 서버에 요청을 보낼 때마다 쿠키를 같이 전송

서버는 클라이언트의 요청에 포함된 쿠키를 확인해 클라이언트를 구분할 수 있음

  • 클라이언트의 IP 주소와 User-Agent는 매번 변경될 수 있는 고유하지 않은 정보일 뿐만 아니라, HTTP 프로토콜의 ConnectionlessStateless 특징 때문에 웹 서버는 클라이언트를 기억할 수 없음
  • ConnectionlessStateless 특성을 갖는 HTTP에서 상태를 유지하기 위해 쿠키(Cookie)가 탄생

HTTP 프로토콜 특징

  • Connectionless특정 요청에 대한 연결은 이후의 요청과 이어지지 않고 새 요청이 있을 때마다 항상 새로운 연결을 맺음
  • 하나의 요청에 하나의 응답을 한 후 연결을 종료하는 것을 의미
  • Stateless이전 연결에서 사용한 데이터를 다른 연결에서 요구할 수 없음
  • 통신이 끝난 후 상태 정보를 저장하지 않는 것을 의미

1.2. 용도

일반적으로 쿠키는 클라이언트의 정보 기록과 상태 정보를 표현하는 용도로 사용

정보 기록

  • 웹 서비스 사용 시 종종 등장하는 팝업 차에 “다시 보지 않기”, “7일 간 표시하지 않기” 버튼이 있는 것을 확인할 수 있음
  • 웹 서버는 각 클라이언트의 팝업 옵션을 기억하기 위해 쿠키에 해당 정보를 기록하고, 쿠키를 통해 팝업 창 표시 여부를 판단
  • 과거에는 클라이언트의 정보를 저장하기 위해 쿠키가 종종 사용 → 쿠키는 서버와 통신할 때마다 전송되기 때문에 쿠키가 필요 없는 요청을 보낼 때 리소스 낭비가 발생할 수 있음
  • 최근에는 단점을 보완하기 위해 Modern Storage APIs를 통해 데이터를 저장하는 방식을 권장

상태 정보

  • 많은 웹 사이트에서는 회원 가입과 로그인을 통해 개개인에게 맞춤형 서비스를 제공
  • 웹 서버에서는 수많은 클라이언트의 로그인 상태와 이용자를 구별해야 하는데, 이때 클라이언트를 식별할 수 있는 값을 쿠키에 저장해 사용

1.3. 쿠키가 없는 통신

서버는 요청을 보낸 클라이언트가 누군지 알 수 없기 때문에 현재 어떤 클라이언트와 통신하는지 알 수 없음

1.4. 쿠키가 있는 통신

클라이언트는 서버에 요청을 보낼 때마다 쿠키를 포함하고, 서버는 해당 쿠키를 통해 클라이언트를 식별

1.5. 쿠키 변조

쿠키는 클라이언트의 브라우저에 저장되고 요청에 포함되는 정보이므로, 악의적인 클라이언트는 쿠키 정보를 변조해 서버에 요청을 보낼 수 있음

만약 서버가 별다른 검증 없이 쿠키를 통해 이용자의 인증 정보를 식별한다면, 공격자가 타 이용자를 사칭해 정보를 탈취할 수 있음

2. 세션

2.1. 세션

쿠키에 인증 상태를 저장하지만 클라이언트가 인증 정보를 변조할 수 없게 하기 위해 세션(Session)을 사용

  • 세션은 인증 정보를 서버에 저장하고 해당 데이터에 접근할 수 있는 키(유추할 수 없는 랜덤한 문자열, Session ID)를 만들어 클라이언트에 전달하는 방식으로 작동
  • 브라우저는 해당 키를 쿠키에 저장하고 이후에 HTTP 요청을 보낼 때 사용
  • 서버는 요청에 포함된 키에 해당하는 데이터를 가져와 인증 상태를 확인

2.2. 쿠키 적용법

  • 쿠키는 클라이언트에 저장되기 때문에 클라이언트는 저장된 쿠키를 조회, 수정, 추가할 수 있음
  • 클라이언트가 서버에 요청을 보낼 때 저장된 쿠키를 요청 헤더에 넣어 전송하기 때문에 이용자가 요청을 보낼 때 쿠키 헤더를 변조할 수 있음
  • 쿠키를 설정할 때에는 만료 시간을 지정할 수 있고, 만료 시간 이후에는 클라이언트에서 쿠키가 삭제됨
  • 쿠키의 만료는 클라이언트(브라우저)에서 관리
  • 쿠키는 서버와 클라이언트 둘 다 설정할 수 있음

서버

HTTP 응답 중 헤더에 쿠키 설정 헤더 (Set-Cookie)를 추가하면 클라이언트의 브라우저가 쿠키를 설정

HTTP/1.1 200 OK
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: name=test;
Set-Cookie: age=30; Expires=Fri, 30 Sep 2022 14:54:50 GMT;
...

클라이언트

자바스크립트를 사용해 쿠키를 설정

document.cookie = "name=test;"
document.cookie = "age=30; Expires=Fri, 30 Sep 2022 14:54:50 GMT;"

2.3. 크롬 Console을 활용하는 법

  • 크롬 페이지에서 우클릭했을 때 표시되는 검사 버튼을 누른 후 Console탭을 누름
  • document.cookie를 입력하면 쿠키 정보를 확인할 수 있음
  • 쿠키 옵션(HttpOnly)에 따라 자바스크립트에서 쿠키 확인이 불가능할 수 있음

2.4. 크롬 Application을 활용하는 법

  • 크롬 페이지에서 우클릭했을 때 표시되는 검사 버튼을 누른 후 Application탭을 누름
  • 좌측에 나열된 목록에서 Cookies를 펼치면 Origin 목록을 확인할 수 있음
  • Origin(https://dreamhack.io)를 누르면 설정 된 쿠키 정보를 확인 / 수정할 수 있음

2.5. 세션 하이재킹(Session Hijacking)

타 이용자의 쿠키를 훔쳐 인증 정보를 획득하는 공격

  • 쿠키에는 세션 정보가 저장되어 있고 서버는 이를 통해 이용자를 식별하고 인증을 처리
  • 공격자가 이용자의 쿠키를 훔칠 수 있으면 세션에 해당하는 이용자의 인증 상태를 훔칠 수 있음2. 세션쿠키에 인증 상태를 저장하지만 클라이언트가 인증 정보를 변조할 수 없게 하기 위해 세션(Session)을 사용

 

Mitigation: Same Origin Policy

1. Same Origin Policy (SOP)

1.1. 동일 출처 정책 (Same Origin Policy, SOP)

  • 브라우저는 인증 정보로 사용될 수 있는 쿠키를 브라우저 내부에 보관
  • 이용자가 웹 서비스에 접속할 때, 브라우저는 해당 웹 서비스에서 사용하는 인증 정보인 쿠키를 HTTP 요청에 포함시켜 전달
  • 이는 사이트에 직접 접속하는 것에만 한정되지 않음
  • 브라우저는 웹 리소스를 통해 간접적으로 타 사이트에 접근할 때도 인증 정보인 쿠키를 함께 전송하는 특징을 가지고 있음
  • 이로 인해 악의적인 페이지가 클라이언트의 권한을 이용해 대상 사이트에 HTTP 요청을 보내고, HTTP 응답 정보를 획득하는 코드를 실행할 수 있음
  • 이는 정보 유출과 같은 보안 위협이 생길 수 있는 요소가 되므로, 클라이언트 입장에서는 가져온 데이터를 악의적인 페이지에서 읽을 수 없도록 해야 함

Same Origin Policy의 오리진(Origin) 구분 방법

오리진은 프로토콜(Protocol, Scheme), 포트(Port), 호스트(Host)로 구성되고, 구성 요소가 모두 일치해야 동일한 오리진임

  • https://same-origin.com/라는 오리진과 비교
    URL 결과 이유
    https://same-origin.com/frame.html Same Origin Path만 다름
    http://same-origin.com/frame.html Cross Origin Scheme이 다름
    https://cross.same-origin.com/frame.html Cross Origin Host가 다름
    https://same-origin.com:1234/ Cross Origin Port가 다름

1.2. Same Origin Policy 실습

SOP는 Cross Origin이 아닌 Same Origin일 때만 정보를 읽을 수 있도록 해줌

  • window.open은 새로운 창을 띄우는 함수
  • object.location.href는 객체가 가리키고 있는 URL 주소를 읽어오는 코드

Same Origin

sameNewWindow = window.open('<https://dreamhack.io/lecture>');
console.log(sameNewWindow.location.href);
결과: <https://dreamhack.io/lecture>

Cross Origin

crossNewWindow = window.open('<https://theori.io>');
console.log(crossNewWindow.location.href);
결과: Origin 오류 발생

Cross Origin 데이터 읽기/쓰기

외부 출처에서 불러온 데이터를 읽으려고 할 때는 오류가 발생해 읽지 못함

읽는 것 외에 데이터를 쓰는 것은 문제 없이 동작함

crossNewWindow = window.open('<https://theori.io>');
crossNewWindow.location.href = "<https://dreamhack.io>";

1.3. Same Origin Policy (SOP) 데모

모듈 구성 코드

<!-- iframe 객체 생성 -->
<iframe src="" id="my-frame"></iframe>

<!-- Javascript 시작 -->
<script>
/* 2번째 줄의 iframe 객체를 myFrame 변수에 가져옵니다. */
let myFrame = document.getElementById('my-frame')

/* iframe 객체에 주소가 로드되는 경우 아래와 같은 코드를 실행합니다. */
myFrame.onload = () => {
    /* try ... catch 는 에러를 처리하는 로직 입니다. */
    try {
        /* 로드가 완료되면, secret-element 객체의 내용을 콘솔에 출력합니다. */
        let secretValue = myFrame.contentWindow.document.getElementById('secret-element').innerText;
        console.log({ secretValue });
    } catch(error) {
        /* 오류 발생시 콘솔에 오류 로그를 출력합니다. */
        console.log({ error });
    }
}
/* iframe객체에 Same Origin, Cross Origin 주소를 로드하는 함수 입니다. */
const loadSameOrigin = () => { myFrame.src = 'https://same-origin.com/frame.html'; }
const loadCrossOrigin = () => { myFrame.src = 'https://cross-origin.com/frame.html'; }
</script>

<!--
버튼 2개 생성 (Same Origin 버튼, Cross Origin 버튼)
-->
<button onclick=loadSameOrigin()>Same Origin</button><br>
<button onclick=loadCrossOrigin()>Cross Origin</button>
<!--
frame.html의 코드가 아래와 같습니다.
secret-element라는 id를 가진 div 객체 안에 treasure라고 하는 비밀 값을 넣어두었습니다.
-->
<div id="secret-element">treasure</div>
 
  1. 두 번째 줄의 iframe은 현재 웹 페이지 안에 또 다른 하나의 웹 페이지를 삽입하는 HTML 태그 src 요소를 설정함으로써 삽입할 웹 페이지의 주소가 결정됨
  2. 10번째 줄의 onload는 이벤트 핸들러로써, 해당 객체가 성공적으로 로드되었을 때 동작 10~23번 줄이 iframe 객체에 페이지가 로드되면 동작하는 코드
  3. 14~15번째 줄은 로드가 완료되면 iframe 내에 삽입된 주소에서 secret-element객체의 값인 treasure를 읽어와 콘솔에 출력하는 동작을 수행

1.4. SOP 실습

Same Origin 버튼

앞선 과정이 모두 성공적으로 수행되어 treasure라는 값이 출력

Cross Origin 버튼

앞선 과정 중, 세 번째 과정이 실패하여 Cross Origin의 데이터에 접근할 수 없다는 오류가 출력됨

2. Cross Origin Resource Sharing (CORS)

2.1. Same Origin Policy 제한 완화

SOP는 클라이언트 사이드 웹 보안에서 중요한 요소 → 브라우저가 SOP에 구애 받지 않고 외부 출처에 대한 접근을 허용해주는 경우가 존재

  • <img>, <style>, <script>등의 태그

웹 서비스에서 동일 출처 정책인 SOP를 완화하여 다른 출처의 데이터를 처리해야 하는 경우도 있음

  • 카페: https://cafe.dreamhack.io
  • 블로그: https://blog.dreamhack.io
  • 메일: https://mail.dreamhack.io
  • 메인: https://dreamhack.io
  • 특정 포털 사이트가 카페, 블로그, 메일 서비스를 위의 주소로 운영하고 있을 때, 각 서비스의 Host가 다르기 때문에 브라우저는 각 사이트의 오리진이 다르다고 인식
  • 이용자가 수신한 메일의 개수를 메인 페이지에 출력하려면, 개발자는 메인 페이지에서 메일 서비스에 관련된 리소스를 요청하도록 해야함
  • 두 사이트는 오리진이 다르므로 SOP를 적용받지 않고 리소스를 공유할 방법이 필요

교차 출처 리소스 공유 (Cross Origin Resource Sharing, CORS)

자원을 공유하기 위해 사용할 수 있는 공유 방법

교차 출처의 자원을 공유하는 방법

  • CORS와 관련된 HTTP 헤더를 추가하여 전송하는 방법 사용
  • JSON with Padding(JSONP) 방법을 통해 CORS를 대체할 수 있음

2.2. Cross Origin Resource Sharing (CORS)

HTTP 헤더에 기반하여 Cross Origin 간에 리소스를 공유하는 방법

발신측에서 CORS 헤더를 설정해 요청하면, 수신측에서 헤더를 구분해 정해진 규칙에 맞게 데이터를 가져갈 수 있도록 설정

웹 리소스 요청 코드

/*
    XMLHttpRequest 객체를 생성합니다. 
    XMLHttpRequest는 웹 브라우저와 웹 서버 간에 데이터 전송을
    도와주는 객체 입니다. 이를 통해 HTTP 요청을 보낼 수 있습니다.
*/
xhr = new XMLHttpRequest();
/* <https://theori.io/whoami> 페이지에 POST 요청을 보내도록 합니다. */
xhr.open('POST', '<https://theori.io/whoami>');
/* HTTP 요청을 보낼 때, 쿠키 정보도 함께 사용하도록 해줍니다. */
xhr.withCredentials = true;
/* HTTP Body를 JSON 형태로 보낼 것이라고 수신측에 알려줍니다. */
xhr.setRequestHeader('Content-Type', 'application/json');
/* xhr 객체를 통해 HTTP 요청을 실행합니다. */
xhr.send("{'data':'WhoAmI'}");

발신 측의 HTTP 요청

OPTIONS /whoami HTTP/1.1
Host: theori.io
Connection: keep-alive
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: <https://dreamhack.io>
Accept: */*
Referer: <https://dreamhack.io/>

서버의 응답

HTTP/1.1 200 OK
Access-Control-Allow-Origin: <https://dreamhack.io>
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type

 

Header 설명
Access-Control-Allow-Origin 헤더 값에 해당하는 Origin에서 들어오는 요청만 처리
Access-Control-Allow-Methods 헤더 값에 해당하는 메소드의 요청만 처리
Access-Control-Allow-Credentials 쿠키 사용 여부를 판단
예시의 경우 쿠키의 사용을 허용  
Access-Control-Allow-Headers 헤더 값에 해당하는 헤더의 사용 가능 여부를 나타냄
  • 발신측에서 POST 방식으로 HTTP 요청을 보냈으나, OPTIONS 메소드를 가진 HTTP 요청이 전달된 것을 확인할 수 있음 (CORS preflight)
  • “Access-Control-Request”로 시작하는 헤더 뒤에 따라오는 Method와 Headers는 각각 메소드와 헤더를 추가적으로 추가적으로 사용할 수 있는지 질의하고, 서버는 응답함
  • 브라우저는 수신측의 응답이 발신측의 요청과 상응하는지 확인하고, 그때야 비로소 POST 요청을 보내 수신측의 웹 리소스를 요청하는 HTTP 요청을 보냄

2.3. JSON with Padding (JSONP)

이미지나 자바스크립트, CSS 등의 리소스가 SOP에 구애 받지 않고 외부 출처에 대해 접근을 허용하는 특징을 이용하여, <script>태그로 Cross Origin의 데이터를 불러옴

<script>태그 내에서는 데이터를 자바스크립트의 코드로 인식하기 때문에 Callback 함수를 활용해야 함

Cross Origin에 요청할 때 callback 파라미터에 어떤 함수로 받아오는 데이터를 핸들링할지 넘겨주면, 대상 서버는 전달된 Callback으로 데이터를 감싸 응답

웹 리소스 요청 코드

/* myCallback이라는 콜백 함수를 지정합니다. */
function myCallback(data){
    /* 전달받은 인자에서 id를 콘솔에 출력합니다.*/
	console.log(data.id)
}

웹 리소스 요청에 따른 응답 코드

/*
수신측은 myCallback 이라는 함수를 통해 요청측에 데이터를 전달합니다.
전달할 데이터는 현재 theori.io에서 클라이언트가 사용 중인 계정 정보인
{'id': 'dreamhack'} 입니다. 
*/
myCallback({'id':'dreamhack'});
  • 요청 코드 13번째 줄에서 Cross Origin의 데이터를 불러옴
  • callback 파라미터로 myCallback을 함께 전달
  • Cross Origin에서는 응답할 데이터를 myCallback 함수의 인자로 전달될 수 있도록 myCallback으로 감싸 Javascript 코드를 반환
  • 반환된 코드는 요청 측에서 실행되기 때문에 3~6번 줄에서 정의된 myCallback 함수가 전달된 데이터를 읽을 수 있음
  • JSONP는 CORS가 생기기 전에 사용하던 방법으로 현재는 거의 사용하지 않는 추세이기 때문에, 새롭게 코드를 작성할 때에는 CORS를 사용해야 함
반응형

'Security Study > Web' 카테고리의 다른 글

[Dreamhack] Command Injection  (0) 2024.04.12
[Dreamhack] SQL Injection  (2) 2024.02.20
[Dreamhack] Cross Site Request Forgery (CSRF)  (2) 2024.02.14
[Dreamhack] Cross-Site-Scripting (XSS)  (2) 2024.02.09
[Dreamhack] Background - Web  (0) 2024.02.04