XSS와 CSRF 차이: 출력 인코딩·CSRF 토큰·SameSite 쿠키

반응형

XSS와 CSRF의 차이는 공격자가 악용하는 신뢰 경계에 있다. XSS는 신뢰하지 않은 데이터가 브라우저에서 코드로 실행되는 문제이고, CSRF는 브라우저가 자동으로 보내는 인증 정보를 이용해 사용자가 의도하지 않은 요청을 보내는 문제다. 방어책도 출력 문맥과 요청 진위를 각각 다뤄야 한다.

XSS와 CSRF는 무엇이 다른가

구분 XSS CSRF
악용하는 것 페이지가 data를 code로 해석하는 지점 cookie 같은 ambient credential의 자동 전송
공격 결과 script 실행, 화면 변조, 계정 동작 대행, data 접근 로그인한 사용자의 권한으로 상태 변경 요청
대표 방어 문맥별 output encoding, safe sink, HTML sanitization framework CSRF 보호, session-bound token, Origin 검증
보조 방어 CSP, HttpOnly cookie SameSite, Fetch Metadata, Origin·Referer 검사
공통 원칙 server-side authorization과 최소 권한은 별도로 필요 server-side authorization과 최소 권한은 별도로 필요

XSS가 있으면 같은 origin에서 정상 사용자처럼 요청을 만들 수 있어 CSRF token을 읽거나 보호된 action을 실행할 가능성이 커진다. 따라서 “CSRF 방어를 했으니 XSS와 무관하다”거나 그 반대로 생각하면 안 된다.

XSS는 어떻게 막아야 하나

OWASP XSS Prevention Cheat Sheet는 하나의 공통 escape 함수를 모든 위치에 적용하지 말고 data가 들어가는 문맥에 맞춰 처리하라고 권고한다.

출력 위치에 맞게 encoding한다

HTML text, HTML attribute, JavaScript string, CSS, URL은 서로 다른 parser가 읽는다. HTML body에 안전한 encoding을 JavaScript 문맥에 그대로 적용해도 안전하다고 보장할 수 없다. 가능하면 framework의 auto-escaping을 유지하고, 신뢰하지 않은 값을 executable context에 직접 넣지 않는다.

DOM을 갱신할 때도 code를 해석하는 sink와 text를 넣는 sink를 구분한다.

// 신뢰하지 않은 값을 HTML로 해석할 수 있다.
result.innerHTML = userInput;

// 값을 text로만 표시한다.
result.textContent = userInput;

HTML을 허용해야 하면 sanitization한다

게시글 편집기처럼 일부 markup을 허용해야 할 때는 모든 태그를 단순 제거하는 대신 검증된 HTML sanitizer와 명시적인 허용 정책을 사용한다. sanitization 이후 값을 다시 조합하거나 위험한 sink로 보내면 보호가 깨질 수 있으므로 처리 순서도 고정해야 한다.

CSP와 cookie 속성은 추가 방어다

Content Security Policy는 허용할 script source와 실행 방식을 제한해 XSS의 영향을 줄일 수 있지만 output encoding과 safe sink를 대신하지 않는다. HttpOnly는 JavaScript가 session cookie를 직접 읽는 일을 어렵게 하지만, 이미 실행된 악성 script가 사용자의 권한으로 화면을 읽거나 요청을 보내는 것까지 막지는 못한다.

CSRF는 어떻게 막아야 하나

CSRF는 browser가 target site의 cookie를 request에 자동으로 실어 보내는 성질을 이용한다. 공격자는 응답 내용을 읽지 못하더라도 송금, 이메일 변경, 권한 부여처럼 상태를 바꾸는 요청을 유도할 수 있다.

OWASP CSRF Prevention Cheat Sheet의 우선순위는 직접 token scheme을 새로 만드는 것이 아니라 framework가 제공하는 검증된 CSRF 보호를 사용하는 것이다.

상태 변경에 GET을 사용하지 않는다

GET, HEAD, OPTIONS 같은 safe method는 상태를 바꾸지 않게 설계한다. 링크 방문만으로 계정 설정이 바뀌는 endpoint가 있으면 SameSite=Lax에서도 top-level navigation 같은 경로가 문제가 될 수 있다.

CSRF token을 server에서 검증한다

서버가 예측하기 어려운 token을 발급하고 사용자의 session과 연결해 검증한다. HTML form에는 hidden field, JavaScript client에는 custom request header를 쓰는 방식이 일반적이다. token의 존재만 확인하거나 사용자 session과 무관한 고정값을 비교하면 방어가 되지 않는다.

요청 출처를 함께 확인한다

상태 변경 endpoint에서 Origin을 allowlist와 정확히 비교하고, 필요한 경우 Referer를 fallback으로 사용한다. Sec-Fetch-Site 같은 Fetch Metadata header는 cross-site 요청을 거르는 보조 신호가 된다. legacy client나 proxy behavior를 고려해 바로 차단하기 전 logging 또는 report mode로 영향을 확인하는 편이 안전하다.

SameSite는 어디까지 막아 주나

SameSite는 cookie를 cross-site request에 실을지 결정하는 속성이다. 여기서 site는 origin과 같지 않다. scheme과 registrable domain을 기준으로 보므로 port가 다르거나 sibling subdomain인 상황을 origin 검사와 똑같이 취급하면 안 된다.

일반적인 전송 범위 주의점
Strict same-site 요청 외부 링크로 들어왔을 때 로그인 문맥이 끊길 수 있음
Lax same-site 요청과 일부 cross-site top-level safe navigation 모든 CSRF를 차단하지 않음
None same-site와 cross-site 요청 Secure가 함께 필요

MDN Set-Cookie 문서SameSite=NoneSecure가 필요하다고 설명한다. session cookie의 기본 예시는 다음처럼 잡을 수 있다.

Set-Cookie: __Host-session=opaque-value; Path=/; Secure; HttpOnly; SameSite=Lax

__Host- prefix를 쓰려면 Secure, Path=/, Domain 미지정 조건을 지켜야 한다. 실제 Strict, Lax, None 선택은 OAuth redirect, embedded service, cross-site login 같은 정상 flow를 먼저 그린 뒤 정한다.

SameSite만으로 CSRF 방어를 끝내면 안 되는 이유도 있다.

  • 같은 registrable domain 아래 신뢰하지 않는 subdomain이 있으면 same-site request가 될 수 있다.
  • Lax는 일부 top-level navigation에 cookie 전송을 허용한다.
  • browser별 호환성과 기본값 처리 차이가 남을 수 있다.
  • XSS가 있으면 same-origin request로 보호를 우회할 수 있다.

실무에서 적용할 방어 순서

  1. framework auto-escaping과 기본 CSRF middleware를 끄지 않는다.
  2. 신뢰하지 않은 data가 들어가는 HTML·attribute·JavaScript·URL 문맥을 목록화한다.
  3. innerHTML 같은 위험 sink를 줄이고 textContent 같은 safe sink로 바꾼다.
  4. HTML 허용 지점은 검증된 sanitizer와 allowlist policy로 제한한다.
  5. 모든 상태 변경 endpoint의 method, authorization, CSRF token 검증을 확인한다.
  6. session cookie에 Secure, HttpOnly, 적절한 SameSite를 설정한다.
  7. Origin·Fetch Metadata 검증과 CSP를 defense in depth로 추가한다.
  8. 실제 login, logout, OAuth callback, embedded flow를 browser test로 회귀 검증한다.

cookie와 session identifier의 역할은 네트워크 응용 계층 식별자, 전송 구간 보호는 TLS 통신 흐름에서 이어서 볼 수 있다. TLS는 network 도청과 변조를 줄이지만 application의 XSS나 CSRF 취약점을 자동으로 고치지는 않는다.

정리

XSS는 data가 browser code로 실행되는 문제이므로 문맥별 output encoding, safe sink, 필요한 HTML sanitization이 중심이다. CSRF는 자동 전송 credential을 악용하므로 framework CSRF token과 server-side 검증이 중심이다. CSP, HttpOnly, SameSite, Fetch Metadata는 유용하지만 각각 주 방어를 보완하는 층으로 사용해야 한다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글