쿠키 보안 옵션 (httpOnly, Secure, SameSite)

쿠키 보안 옵션 (httpOnly, Secure, SameSite)

빠른 비교표

옵션 목적 기본값 설정 효과
httpOnly XSS 공격 방지 false httpOnly: true JavaScript 접근 불가
Secure 중간자 공격 방지 false secure: true HTTPS만 전송
SameSite CSRF 공격 방지 Lax sameSite: 'strict' 다른 사이트 요청 차단

상세 설명표

1. httpOnly

항목 설명
용도 XSS (Cross-Site Scripting) 공격 방지
설정 res.cookie('token', val, { httpOnly: true })
기본값 false (설정하지 않으면 JavaScript 접근 가능)
효과 JavaScript에서 document.cookie로 접근 불가
읽기 ❌ JavaScript 불가 / ✅ HTTP 요청에 자동 포함
쓰기 ❌ JavaScript에서 설정 불가
공격 방어 악성 스크립트가 실행되어도 쿠키 탈취 불가
권장 ⭐⭐⭐⭐⭐ (Refresh Token 저장 시 필수)
예시 httpOnly: true

httpOnly 예시

// 서버
res.cookie('refreshToken', token, { httpOnly: true });

// 클라이언트 - 접근 시도
document.cookie;  // ❌ 쿠키 보이지 않음
// ""

// 하지만 HTTP 요청에는 자동 포함
GET /api/user HTTP/1.1
Cookie: refreshToken=xyz789  // ✅ 자동 포함

// 서버에서는 받을 수 있음
req.cookies.refreshToken  // ✅ "xyz789"

2. Secure

항목 설명
용도 중간자 공격(Man-in-the-Middle) 방지
설정 res.cookie('token', val, { secure: true })
기본값 false (HTTP도 전송)
효과 HTTPS 연결에서만 쿠키 전송
HTTP ❌ HTTP 요청에 쿠키 포함 안 됨
HTTPS ✅ HTTPS 요청에 쿠키 포함
공격 방어 암호화되지 않은 연결로 쿠키 탈취 방지
권장 ⭐⭐⭐⭐⭐ (프로덕션 필수)
예시 secure: process.env.NODE_ENV === 'production'

Secure 예시

// 서버
res.cookie('token', token, { secure: true });

// 개발 환경 (localhost:3000 - HTTP)
GET http://localhost:3000/api/user
Cookie: (없음)  // ❌ 전송 안 됨

// 프로덕션 (https://api.example.com - HTTPS)
GET https://api.example.com/api/user
Cookie: token=xyz789  // ✅ 전송됨

3. SameSite

항목 설명
용도 CSRF (Cross-Site Request Forgery) 공격 방지
설정 res.cookie('token', val, { sameSite: 'strict' })
기본값 ‘Lax’ (일부 크로스 사이트 요청에 포함)
값 – Strict ❌ 모든 크로스 사이트 요청에서 쿠키 미포함 (가장 안전)
값 – Lax ⚠️ 안전한 메서드(GET)의 크로스 사이트만 포함 (기본값)
값 – None ✅ 모든 크로스 사이트 요청에 포함 (Secure 필수)
공격 방어 악의적인 웹사이트에서의 자동 요청 차단
권장 ⭐⭐⭐⭐⭐ (‘strict’ 또는 ‘lax’ 권장)
예시 sameSite: 'strict'

SameSite 값 비교

SameSite 같은 사이트 크로스 사이트 (GET) 크로스 사이트 (POST) 보안
Strict ⭐⭐⭐ (매우 강함)
Lax (기본) ⭐⭐ (중간)
None ⭐ (약함, Secure 필수)

SameSite 예시

// 서버
res.cookie('token', token, { sameSite: 'strict' });

// 정상 요청 (같은 사이트)
GET https://example.com/api/user
Cookie: token=xyz789  // ✅ 포함됨

// 악의적인 요청 (다른 사이트에서)
// hacker.com에서 example.com으로 자동 요청
GET https://example.com/api/user
Cookie: token=xyz789  // ❌ Strict이면 미포함

CSRF 공격 예시 (SameSite로 방어)

SameSite 없을 때 (위험)

<!-- hacker.com (악의적인 사이트) -->
<img src="https://bank.com/api/transfer?amount=1000&to=hacker" />

<!-- bank.com의 쿠키가 자동으로 포함됨 -->
GET https://bank.com/api/transfer?amount=1000&to=hacker
Cookie: sessionToken=xyz789  // ❌ 자동 포함 (위험!)

<!-- 공격 성공: 사용자 모르게 돈 이체됨 -->

SameSite: strict 설정 시 (안전)

<!-- hacker.com (악의적인 사이트) -->
<img src="https://bank.com/api/transfer?amount=1000&to=hacker" />

<!-- bank.com의 쿠키가 포함 안 됨 -->
GET https://bank.com/api/transfer?amount=1000&to=hacker
Cookie: (없음)  // ✅ 쿠키 미포함 (안전!)

<!-- 공격 실패: 인증 없이 요청 거부됨 -->

실제 구현 코드

개발 환경 설정

// server.js
app.post('/api/login', (req, res) => {
  const token = jwt.sign({ userId: 1 }, SECRET);

  // 개발/프로덕션 구분
  const isProduction = process.env.NODE_ENV === 'production';

  res.cookie('refreshToken', token, {
    httpOnly: true,        // ✅ XSS 방지
    secure: isProduction,  // ✅ HTTPS에서만 전송 (프로덕션)
    sameSite: 'strict',    // ✅ CSRF 방지
    maxAge: 7 * 24 * 60 * 60 * 1000  // 7일
  });

  res.json({ accessToken: token });
});

프로덕션 환경 권장 설정

res.cookie('refreshToken', token, {
  httpOnly: true,       // 필수
  secure: true,         // HTTPS만
  sameSite: 'strict',   // 크로스 사이트 차단
  domain: 'example.com',  // 특정 도메인만
  path: '/',            // 루트 경로
  maxAge: 7 * 24 * 60 * 60 * 1000
});

공격 유형별 방어 정리

공격 유형 설명 방어 방법 옵션
XSS JavaScript로 쿠키 탈취 JavaScript 접근 차단 httpOnly: true
중간자 공격 암호화 없는 연결에서 탈취 암호화 통신만 허용 secure: true
CSRF 다른 사이트에서 자동 요청 크로스 사이트 요청 차단 sameSite: 'strict'

요약 표

각 옵션의 필요성

httpOnly
├─ 보호: XSS 공격
├─ 원리: JavaScript 접근 불가
├─ 자동 전송: ✅ (HTTP 요청에 자동 포함)
└─ 필수도: ⭐⭐⭐⭐⭐

Secure
├─ 보호: 중간자 공격
├─ 원리: HTTPS만 전송
├─ 자동 전송: ✅ (HTTPS 연결에서만)
└─ 필수도: ⭐⭐⭐⭐⭐

SameSite
├─ 보호: CSRF 공격
├─ 원리: 크로스 사이트 요청 차단
├─ 자동 전송: ⚠️ (설정값에 따라 다름)
└─ 필수도: ⭐⭐⭐⭐

결론: 권장 설정

// ✅ 프로덕션 환경 최상의 보안
res.cookie('refreshToken', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000
});

// ✅ 개발 환경 (localhost)
res.cookie('refreshToken', token, {
  httpOnly: true,
  secure: false,        // localhost에서는 필요 없음
  sameSite: 'lax',      // 개발 편의성
  maxAge: 7 * 24 * 60 * 60 * 1000
});

댓글 남기기