stack-analysis
CHAPTER 107 / 120
읽기 약 2분
FUNCTION
CSRF 방어: 토큰 + SameSite 쿠키
핵심 개념
CSRF 공격·token·SameSite·double submit·실전 적용 — 위조 요청 차단.
본문
CSRF 공격 흐름
1. 사용자가 example.com에 로그인 (쿠키)
2. 사용자가 evil.com 방문
3. evil.com 페이지에서:
<form action="https://example.com/transfer" method="POST">
<input name="to" value="hacker">
<input name="amount" value="1000000">
</form>
<script>document.forms[0].submit()</script>
4. 브라우저 자동으로 example.com 쿠키 첨부
5. example.com → 위조 거래 처리
→ 사용자 인지 없이 행동 수행방어 1: SameSite Cookie (가장 쉬움)
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict', // 다른 사이트 요청 시 쿠키 전송 X
});
// SameSite 옵션:
// - 'strict': 다른 도메인 요청 시 쿠키 전송 안 함 (가장 안전)
// - 'lax': GET·top-level navigation만 허용 (기본 권장)
// - 'none': 모든 요청에 전송 (CSRF 위험)
// 'lax'가 균형:
// - 외부 링크 클릭 시 로그인 유지
// - POST·iframe·XHR은 차단방어 2: CSRF Token (이중)
import csrf from 'csurf';
app.use(csrf({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } }));
// 폼에 토큰 주입
app.get('/transfer-form', (req, res) => {
res.render('transfer', { csrfToken: req.csrfToken() });
});
// HTML
<form method="POST" action="/transfer">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
...
</form>
// → POST 시 토큰 검증
// → 다른 사이트는 토큰 모름 → 거부Double Submit Cookie 패턴
// 쿠키 + 헤더 양쪽에 토큰
// 다른 사이트는 쿠키 읽지 못함 → 헤더 매칭 실패
// 발급
app.post('/login', (req, res) => {
const csrfToken = crypto.randomUUID();
res.cookie('csrf', csrfToken, { httpOnly: false, sameSite: 'lax' });
// ...
});
// 검증 미들웨어
app.use((req, res, next) => {
if (['GET', 'HEAD'].includes(req.method)) return next();
const cookieToken = req.cookies.csrf;
const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || cookieToken !== headerToken) {
return res.status(403).json({ error: 'csrf_failed' });
}
next();
});
// 클라이언트
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': getCookie('csrf'),
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(data),
});Origin / Referer 검증
function checkOrigin(req: Request, res: Response, next: NextFunction) {
const origin = req.headers.origin || req.headers.referer;
const allowed = [
'https://example.com',
'https://www.example.com',
];
if (req.method !== 'GET' && origin) {
const url = new URL(origin);
if (!allowed.includes(url.origin)) {
return res.status(403).end();
}
}
next();
}API 토큰 (Bearer) — CSRF 자동 면역
// JWT를 Authorization 헤더로 보내면 CSRF 불가
// 브라우저가 자동으로 헤더 추가하지 않음
fetch('/api/transfer', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify(data),
});
// → 다른 사이트가 헤더 추가 못 함 → CSRF 자동 방어
// → 단, accessToken이 localStorage면 XSS 위험SPA — 권장 패턴
[1순위] Bearer Token + httpOnly Refresh Cookie
- accessToken: JS 메모리 (페이지 새로고침 시 refresh)
- refreshToken: httpOnly Secure SameSite=Strict cookie
- → CSRF 면역 + XSS 보호
[2순위] Session Cookie + CSRF Token
- session: httpOnly Secure SameSite=Lax
- csrf: 헤더로 검증
- → 전통적 + 안전GraphQL · tRPC
// GraphQL — 동일 정책
// POST /graphql 모든 mutation에 CSRF 보호
// tRPC — Origin 검증 + Bearer
import { createTRPCContext } from '@trpc/server';
const t = initTRPC.context<Context>().create({
errorFormatter({ shape }) { return shape; },
});
// 미들웨어로 Origin 검증
const isFromAllowedOrigin = t.middleware(({ ctx, next }) => {
const origin = ctx.req.headers.origin;
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
throw new TRPCError({ code: 'FORBIDDEN' });
}
return next();
});흔한 실수
1. SameSite=None + Secure 누락 → 쿠키 전송 안 됨
2. SameSite=Strict인데 외부 링크 클릭 시 로그인 풀림
→ Lax 사용
3. CSRF 토큰을 일회용으로 → 사용자 짜증
→ 세션 단위 또는 시간 기반
4. GET 요청에 변경 동작 (security through obscurity)
→ POST/PUT/DELETE 사용 + CSRF 검증다음 챕터
CH.108 "파일 업로드 보안".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 CSRF 방어 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
CSRF 방어 관련 베스트 프랙티스 5가지를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 CSRF 방어 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 시장의 CSRF 방어 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
CSRF 방어: 토큰 + SameSite 쿠키는 이 3가지만 확실히 잡으세요
1.SameSite=Lax cookie = 90% CSRF 방어 — 기본 적용
2.CSRF Token (csurf) + Double Submit = 이중 안전망
3.Bearer Token (Authorization 헤더) = CSRF 자동 면역
공유하기
진행도 107 / 120