핵심 요약
CSRF는 로그인 쿠키처럼 브라우저가 자동으로 보내는 인증 정보를 악용해 사용자가 의도하지 않은 상태 변경 요청을 만들게 하는 공격입니다. XSS는 신뢰할 수 없는 데이터가 HTML이나 자바스크립트로 해석되어 피해자의 브라우저에서 실행되는 공격입니다. 구분 기준은 ‘요청의 의도와 출처를 검증하는가’와 ‘데이터가 실행 가능한 코드가 되는가’입니다. 이 글은 2026년 7월 19일 확인한 OWASP와 MDN 문서를 기준으로 공격 흐름, 코드 검토 지점, 방어 체크리스트를 정리합니다.

- CSRF와 XSS를 구분하는 기준
- CSRF 공격 흐름과 방어
- XSS 공격 흐름과 방어
- 프론트엔드 구현에서 놓치기 쉬운 지점
- 구현·검증 체크리스트
- 면접에서 설명하는 방법
- 자주 묻는 질문
- 검증 기준과 참고 자료
- 결론
CSRF와 XSS를 구분하는 기준
두 공격은 모두 사용자의 브라우저를 매개로 하지만 신뢰 경계가 다릅니다. CSRF에서는 공격자가 피해 사이트의 쿠키를 읽지 못하더라도 브라우저가 쿠키를 자동 첨부하는 성질을 이용할 수 있습니다. XSS에서는 공격자가 주입한 데이터가 문서 안에서 코드로 해석되는 지점, 즉 위험한 출력 컨텍스트나 DOM sink가 핵심입니다.
| 구분 | CSRF | XSS |
|---|---|---|
| 핵심 질문 | 이 상태 변경 요청이 신뢰할 수 있는 흐름에서 시작됐는가? | 신뢰할 수 없는 데이터가 코드로 실행되는가? |
| 주요 전제 | 브라우저가 세션 쿠키 등 인증 정보를 자동 전송함 | 공격자 입력이 안전하게 인코딩·정화되지 않은 채 위험한 컨텍스트에 도달함 |
| 주요 실행 지점 | 피해자의 권한으로 처리되는 서버 요청 | 피해자의 브라우저와 DOM |
| 대표 방어 | 서버 검증 CSRF 토큰, SameSite 쿠키, Origin·Fetch Metadata 검증, 재인증 | 컨텍스트별 출력 인코딩, 안전한 DOM API, HTML 정화, CSP |
둘은 독립적이지만 완전히 분리되지는 않습니다. 같은 출처에서 실행되는 XSS 코드는 페이지의 CSRF 토큰을 읽거나 정상 요청 함수를 호출할 수 있어 CSRF 방어를 우회할 수 있습니다. OWASP도 XSS가 CSRF 완화책을 무력화할 수 있다고 명시합니다. 따라서 한쪽 방어를 다른 쪽 방어의 대체재로 보면 안 됩니다.
CSRF 공격 흐름과 방어
공격은 인증 정보 탈취 없이도 성립할 수 있습니다
CSRF는 Cross-Site Request Forgery의 약자입니다. 사용자가 피해 사이트에 로그인한 상태에서 공격자가 만든 페이지를 열면, 그 페이지가 상태 변경 요청을 전송하도록 만들 수 있습니다. 대상 서버가 쿠키 기반 인증만 확인하고 요청의 출처나 CSRF 토큰을 검증하지 않으면 요청을 정상 사용자 행동으로 오인할 수 있습니다.
1. 사용자가 대상 서비스에 로그인한다.
2. 브라우저에 세션 쿠키가 남아 있다.
3. 사용자가 공격자가 만든 페이지를 연다.
4. 페이지가 대상 서비스로 상태 변경 요청을 보낸다.
5. 서버가 요청의 의도를 별도로 검증하지 않으면 피해자의 권한으로 처리될 수 있다.
아래 코드는 원리를 설명하기 위한 교육용 예시입니다. 실제 서비스나 허가받지 않은 시스템에 사용해서는 안 됩니다.
<form action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="100000">
</form>
<script>document.forms[0].submit();</script>
CSRF가 성공해도 공격 능력은 해당 사용자가 서비스에서 할 수 있는 동작과 취약한 엔드포인트의 범위 안에 제한됩니다. 반대로 비밀번호 변경, 결제, 이메일 변경처럼 영향이 큰 기능이라면 피해가 커질 수 있으므로 별도 확인과 재인증이 필요합니다.
토큰은 프론트엔드가 보내고 서버가 검증합니다
가장 먼저 프레임워크가 제공하는 CSRF 보호 기능을 확인합니다. 쿠키로 인증하는 상태 저장형 애플리케이션은 일반적으로 서버가 예측하기 어려운 토큰을 세션과 연결해 발급하고, 모든 상태 변경 요청에서 토큰을 비교합니다. 토큰이 없거나 일치하지 않으면 서버가 요청을 거부해야 합니다. 세션 상태를 두기 어려운 구조라면 단순 double-submit보다 세션에 바인딩한 서명 방식 등 위협 모델에 맞는 패턴을 검토합니다.
<form method="POST" action="/profile/update">
<input type="hidden" name="csrfToken" value="{{SERVER_ISSUED_TOKEN}}">
<input type="text" name="nickname">
<button type="submit">저장</button>
</form>
await fetch('/profile/update', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({ nickname: 'haebi' }),
});
쿠키 속성도 함께 점검합니다. 다음 예시는 HTTPS의 호스트 전용 세션 쿠키를 가정합니다. HttpOnly는 자바스크립트의 쿠키 읽기를 제한하고, Secure는 HTTPS 전송을 요구하며, SameSite는 교차 사이트 요청에서 쿠키 전송 범위를 제한합니다.
Set-Cookie: __Host-sessionId=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
SameSite는 서비스의 로그인·결제·외부 연동 흐름에 따라 Lax, Strict, None 중 선택해야 합니다. SameSite=None에는 Secure가 필요합니다. 또한 SameSite는 단독 보장으로 표현하면 안 되며, CSRF 토큰과 Origin/Referer 또는 Fetch Metadata 검증 같은 서버 측 통제와 함께 적용하는 편이 안전합니다. 자세한 기준은 OWASP CSRF Prevention Cheat Sheet와 MDN Set-Cookie 문서에서 확인할 수 있습니다.
XSS 공격 흐름과 방어
신뢰할 수 없는 데이터가 코드로 해석되는 경로를 찾습니다
XSS는 Cross-Site Scripting의 약자입니다. 저장형 XSS는 게시글이나 댓글처럼 서버에 저장된 입력이 여러 사용자에게 출력될 때, 반사형 XSS는 요청 값이 응답에 바로 반영될 때, DOM 기반 XSS는 클라이언트 자바스크립트가 URL이나 메시지 같은 공격자 제어 값을 위험한 DOM API에 전달할 때 발생할 수 있습니다. 따라서 “서버가 악성 스크립트를 돌려준다”는 설명만으로는 DOM 기반 XSS를 포함하지 못합니다.
아래 예시는 innerHTML에 신뢰할 수 없는 문자열을 넣어 이벤트 처리기가 실행될 수 있는 취약한 흐름입니다. 실습은 본인이 소유하거나 명시적으로 허가받은 로컬 환경에서만 해야 합니다.
// 취약한 예: 공격자 입력을 HTML로 해석한다.
const userInput = '<img src=x onerror="alert(\'XSS\')">';
output.innerHTML = userInput;
텍스트를 표시하려는 목적이라면 HTML로 해석하지 않는 API를 사용합니다.
const userInput = '<img src=x onerror="alert(\'XSS\')">';
output.textContent = userInput;
출력 컨텍스트에 맞게 인코딩하고 HTML이 필요하면 정화합니다
XSS 방어는 입력 문자열을 한 번 필터링하는 것으로 끝나지 않습니다. 같은 값이라도 HTML 본문, 속성, URL, CSS, 자바스크립트 문자열에서 필요한 처리가 다릅니다. 가능한 경우 템플릿과 프레임워크의 기본 자동 이스케이프를 유지하고, textContent처럼 안전한 sink를 사용합니다. 사용자 제공 HTML을 실제 마크업으로 허용해야 한다면 검증된 HTML sanitizer와 명시적인 허용 목록을 사용하고, 정화한 뒤 문자열을 다시 이어 붙이거나 변형해 안전성을 깨지 않도록 합니다.
React의 일반 JSX 텍스트 출력은 기본적으로 이스케이프되지만, dangerouslySetInnerHTML 같은 탈출구, 위험한 URL 스킴, 오래된 플러그인까지 자동으로 안전해지는 것은 아닙니다. 다음과 같이 신뢰 경계가 불명확한 값을 그대로 전달하는 코드는 피해야 합니다.
// 정화되지 않은 사용자 HTML에는 사용하지 않는다.
<div dangerouslySetInnerHTML={{ __html: userInput }} />
CSP는 허용된 스크립트 실행 범위를 제한해 XSS의 가능성이나 영향을 줄이는 심층 방어 수단입니다. 하지만 컨텍스트별 인코딩과 HTML 정화를 대신하지는 않습니다. 기존 사이트에 적용할 때는 Report-Only로 위반 보고를 먼저 살핀 뒤 nonce 또는 hash 기반 정책을 검토하고, 무분별한 'unsafe-inline' 허용을 피합니다. 자세한 방어 규칙은 OWASP XSS Prevention Cheat Sheet와 MDN Content Security Policy 가이드를 기준으로 삼을 수 있습니다.
프론트엔드 구현에서 놓치기 쉬운 지점

보조 통제를 단독 해결책으로 오해하지 않습니다
- CORS만으로 CSRF를 막을 수 있다고 가정하지 않습니다. 브라우저가 교차 출처 응답을 읽지 못하더라도 일부 단순 요청은 전송될 수 있습니다. 상태 변경에는 안전 메서드인 GET을 사용하지 말고 서버 검증을 둡니다.
- HttpOnly가 XSS 전체를 막는다고 가정하지 않습니다. 쿠키 읽기는 제한하지만 같은 출처에서 실행된 스크립트가 사용자 권한으로 요청을 보내거나 화면을 바꾸는 행위까지 차단하지는 못합니다.
- CSRF 토큰을 클라이언트에 넣는 것만으로 끝내지 않습니다. 서버의 세션 연결, 예측 불가능성, 비교와 거부 로직이 함께 있어야 합니다. 토큰 값을 로그나 URL에 노출하지 않습니다.
- 입력 검증과 출력 인코딩을 같은 처리로 보지 않습니다. 입력 검증은 데이터 품질과 허용 형식을 제한하지만, XSS 방어에는 실제 출력 컨텍스트에 맞는 인코딩 또는 정화가 필요합니다.
- 인증과 권한 검사를 혼동하지 않습니다. CSRF 방어는 위조 요청을 줄이지만 사용자가 해당 작업을 수행할 권한이 있는지 확인하는 서버 측 인가를 대체하지 않습니다.
구현·검증 체크리스트
요청 계층
- 상태 변경 엔드포인트가 GET이 아닌 POST, PUT, PATCH, DELETE를 사용하며 서버에서 권한을 다시 확인하는가?
- 프레임워크의 CSRF 보호 기능을 우선 사용하고, 토큰 누락·불일치 요청을 서버가 거부하는가?
- 세션 쿠키에 서비스 흐름에 맞는
Secure,HttpOnly,SameSite,Path,Domain범위를 설정했는가? Origin/Referer또는 Fetch Metadata 검증과 민감 작업의 재인증을 위협 수준에 맞게 추가했는가?- 교차 출처 연동, 웹훅, 결제 콜백처럼 의도적으로 예외인 경로를 전역 정책과 분리했는가?
렌더링 계층
- URL, API 응답, 게시글, 댓글,
postMessage등 신뢰할 수 없는 데이터의 source와 최종 sink를 추적했는가? innerHTML,outerHTML,document.write, 인라인 이벤트 처리기와 프레임워크의 HTML 탈출구 사용을 검색했는가?- HTML 본문·속성·URL·CSS·자바스크립트 컨텍스트별로 올바른 인코딩 또는 허용 목록 검증을 적용했는가?
- HTML 입력이 꼭 필요할 때 검증된 sanitizer를 사용하고, 정화 결과를 다시 위험하게 조합하지 않는가?
- CSP를 보조 통제로 배포하고 리포트를 확인한 뒤 실제 차단 정책을 단계적으로 적용했는가?
수정 뒤에는 실패 조건을 직접 확인합니다
본인이 소유하거나 테스트 권한이 있는 환경에서 CSRF 토큰을 제거하거나 바꾼 요청이 거부되는지, 교차 사이트 폼 요청이 상태를 바꾸지 못하는지 확인합니다. XSS는 무해한 표식 문자열을 각 출력 컨텍스트에 넣어 코드가 아닌 텍스트로 표시되는지 검사합니다. 자동 테스트만으로 끝내지 말고 브라우저 개발자 도구의 네트워크 요청, 응답 헤더, CSP 보고도 함께 봅니다.
면접에서 설명하는 방법
한 문장 답변: “CSRF는 브라우저가 자동 전송하는 인증 정보를 이용해 피해자의 권한으로 의도하지 않은 요청을 보내게 하는 공격이고, XSS는 신뢰할 수 없는 데이터가 피해자의 브라우저에서 실행 가능한 코드가 되는 공격입니다.”
이어지는 질문에서는 방어 책임을 나눠 설명하면 좋습니다. CSRF는 프론트엔드가 토큰을 요청에 포함하더라도 서버가 검증하고 거부해야 합니다. XSS는 프레임워크의 자동 이스케이프를 유지하고 위험한 sink를 피하며, HTML이 필요할 때 정화하고 CSP를 심층 방어로 적용합니다. 마지막으로 XSS가 같은 출처의 정상 요청을 호출해 CSRF 방어를 우회할 수 있다는 상호작용까지 덧붙이면 개념을 단순 암기가 아닌 위협 모델로 이해했다는 점이 드러납니다.
자주 묻는 질문
CSRF 토큰이 있으면 XSS도 막을 수 있나요?
아닙니다. CSRF 토큰은 위조된 상태 변경 요청을 검증하기 위한 수단입니다. 브라우저에서 신뢰할 수 없는 코드가 실행되는 XSS는 출력 인코딩, 안전한 DOM API, HTML 정화와 CSP로 별도 방어해야 합니다.
React를 사용하면 XSS에서 자동으로 안전한가요?
일반 JSX 텍스트 출력은 기본 이스케이프의 도움을 받지만 완전한 보장은 아닙니다. dangerouslySetInnerHTML, 공격자 제어 URL, 직접 DOM 조작, 취약한 의존성처럼 보호 경계를 벗어나는 지점을 따로 검토해야 합니다.
Authorization 헤더를 쓰는 API도 CSRF에 취약한가요?
브라우저가 인증 정보를 자동 첨부하지 않고 신뢰된 코드가 명시적으로 헤더를 넣는 구조라면 전형적인 쿠키 기반 CSRF 조건과 다릅니다. 다만 토큰 저장 위치와 XSS 위험, CORS 설정, 갱신 토큰 쿠키 사용 여부에 따라 위협 모델이 달라지므로 “헤더를 쓴다”는 사실만으로 안전하다고 단정할 수 없습니다.
관련 글로 이어서 확인하기
웹 보안은 한 공격 이름보다 인증·인가·패치 운영을 함께 봐야 합니다. 로그인 상태와 비밀번호 재설정 흐름은 Firebase Authentication 사용 기준에서, 클라이언트 입력과 무관하게 서버 측 권한을 강제하는 구조는 Firebase 보안 규칙 설계에서 이어서 확인할 수 있습니다. 프레임워크 취약점과 실제 사용 기능을 연결해 점검하는 방법은 Next.js 보안 패치 영향 범위 점검을 참고하세요.
검증 기준과 변경 이력
검증 기준일: 2026년 7월 19일. 이 글은 특정 프레임워크 버전의 구현을 보장하는 문서가 아니라 브라우저 기반 웹 애플리케이션의 일반 위협 모델을 설명합니다. 실제 구현에서는 사용 중인 프레임워크와 인증 라이브러리의 최신 공식 보안 지침을 함께 확인해야 합니다.
- OWASP Cross-Site Request Forgery Prevention Cheat Sheet
- OWASP Cross Site Scripting Prevention Cheat Sheet
- MDN Set-Cookie header
- MDN Content Security Policy guide
변경 이력: 2026년 7월 19일에 본문에서 누락된 CSRF·XSS 리터럴과 빈 비교 표를 복구하고, 재현성이 낮은 XSS 예시를 교정했습니다. CSRF 토큰의 서버 검증 책임, SameSite·HttpOnly·CSP의 한계, DOM 기반 XSS와 공식 근거, 구현·검증 체크리스트를 보강했습니다.
결론
CSRF는 인증된 브라우저가 보낸 요청이 사용자가 의도한 것인지 검증하는 문제이고, XSS는 신뢰할 수 없는 데이터가 브라우저에서 코드가 되지 않도록 만드는 문제입니다. 실무에서는 먼저 인증 방식과 데이터 흐름을 그린 뒤, 상태 변경 요청에는 서버 검증 토큰과 쿠키·출처 정책을 적용하고, 렌더링 경로에는 컨텍스트별 인코딩과 안전한 sink를 적용합니다. 마지막으로 CSP와 민감 작업 재인증 같은 심층 방어를 더하고, 토큰 누락 요청과 위험한 출력 문자열이 실제로 차단되는지 테스트해야 합니다.