보안

크로스사이트 스크립팅 - Cross Site Scripting(XSS)

타우루스 2025. 9. 2. 22:59

안녕하세요! 크로스사이트 스크립팅(XSS)에 대해 설명해 드리겠습니다. XSS는 웹 애플리케이션에서 흔히 발견되는 취약점 중 하나로, 공격자가 악성 스크립트를 웹 페이지에 삽입하여 사용자 브라우저에서 실행되도록 하는 공격 방식입니다.


1. XSS란 무엇인가요?

XSS는 웹사이트 관리자가 아닌 공격자가 웹 페이지에 클라이언트 측 스크립트(주로 JavaScript)를 삽입할 수 있을 때 발생합니다. 이 악성 스크립트는 해당 웹 페이지를 방문하는 다른 사용자들의 브라우저에서 실행됩니다. 마치 누군가 합법적인 우편물에 악성 코드가 담긴 쪽지를 몰래 끼워 넣어 보내는 것과 같습니다.

이 이미지에서는 XSS 공격의 전체적인 흐름을 보여줍니다.

  1. Attacker Injects Script: 공격자가 취약한 웹사이트에 악성 스크립트를 주입합니다.
  2. Malicious Script on Server: 주입된 스크립트가 서버에 저장되거나 요청에 반영됩니다.
  3. Victim Browser Runs Script: 피해자가 해당 웹 페이지에 접근하면, 피해자의 브라우저에서 악성 스크립트가 실행됩니다.
  4. Data Exfiltration: 악성 스크립트는 피해자의 쿠키나 세션 정보를 훔쳐 공격자에게 전송할 수 있습니다.

2. XSS 공격의 종류

XSS 공격은 크게 3가지 유형으로 나눌 수 있습니다.
 

2.1. Stored XSS (저장형 XSS)

가장 위험한 유형 중 하나로, 공격자가 삽입한 악성 스크립트가 웹 서버에 영구적으로 저장됩니다. 예를 들어, 게시판이나 댓글 기능에 악성 스크립트가 포함된 글을 작성하면, 다른 사용자들이 해당 글을 볼 때마다 스크립트가 실행됩니다.

이 이미지는 저장형 XSS의 과정을 보여줍니다.

  1. Attacker Injects Script: 공격자가 포럼 게시물 등에 악성 스크립트( <script>stealData();</script>)를 삽입합니다.
  2. Script Stored on Server: 이 스크립트가 웹 서버의 데이터베이스(예: 댓글 테이블)에 저장됩니다.
  3. Victim Views Infected Page: 다른 사용자가 해당 포럼 페이지를 방문하면, 서버에서 스크립트가 포함된 페이지를 전송하고 피해자의 브라우저에서 스크립트가 실행됩니다.
  4. Victim Browser Runs Script: 실행된 스크립트는 피해자의 쿠키나 데이터를 탈취하여 공격자에게 전송할 수 있습니다.

2.2. Reflected XSS (반사형 XSS)

가장 흔하게 발견되는 유형으로, 공격자가 악성 스크립트를 URL 파라미터 등에 포함시켜 사용자에게 링크를 보냅니다. 사용자가 이 링크를 클릭하면, 서버는 스크립트가 포함된 요청을 처리하고 결과를 사용자 브라우저로 반사(reflect)합니다. 이때 스크립트가 실행됩니다.

이 이미지는 반사형 XSS의 과정을 설명합니다.

  1. Attacker Crafts Link: 공격자가 악성 스크립트( <script>alert('XSS')</script> )를 포함한 URL을 생성하여 이메일이나 피싱을 통해 피해자에게 보냅니다. (예: https://example.com/search?query= alert('XSS')</script>)
  2. Victim Clicks Link: 피해자가 해당 링크를 클릭합니다.
  3. Script Reflected on Server: 피해자의 브라우저가 해당 요청을 서버로 보내고, 서버는 URL의 스크립트 부분을 포함하여 응답 페이지를 생성합니다.
  4. Browser Runs Script: 피해자의 브라우저가 응답을 받아 스크립트를 실행합니다. 이로 인해 경고창이 뜨거나, 피해자의 쿠키가 탈취될 수 있습니다.

2.3. DOM-based XSS (DOM 기반 XSS)

이 유형은 서버를 거치지 않고 클라이언트 측에서, 즉 사용자 브라우저의 DOM(Document Object Model) 환경에서 스크립트가 실행될 때 발생합니다. 웹 페이지의 JavaScript 코드가 사용자의 입력값을 제대로 처리하지 못할 때 발생할 수 있습니다.

이 이미지는 DOM 기반 XSS의 과정을 보여줍니다.

  1. User Interacts with Page: 사용자가 웹 페이지의 입력 필드에 악성 스크립트를 입력합니다. (예: <script>alert('XSS')</script>) 이 과정에서 서버와의 직접적인 상호작용은 없습니다.
  2. Client-Side Scripting: 페이지 내의 클라이언트 측 JavaScript 코드가 URL의 해시(#) 부분이나 다른 DOM 요소를 읽어들여 페이지 내용을 동적으로 업데이트합니다. 이때, 취약한 JavaScript 코드는 입력된 스크립트를 제대로 검증하지 않고 DOM에 삽입합니다.
  3. Document Object Model (DOM): 악성 스크립트가 브라우저의 DOM에 직접 삽입됩니다.
  4. Browser Manipulation: 브라우저가 DOM에 삽입된 스크립트를 실행하여, 경고창이 뜨거나 사용자의 쿠키를 탈취하는 등의 악성 행위가 발생합니다.

3. XSS 공격의 위험성

XSS 공격이 성공하면 다음과 같은 심각한 문제들이 발생할 수 있습니다.

  • 세션 하이재킹 (Session Hijacking): 사용자의 세션 쿠키를 탈취하여 사용자의 계정을 가로챕니다.
  • 피싱 (Phishing): 사용자를 가장하여 민감한 정보를 요구하는 가짜 로그인 페이지를 띄울 수 있습니다.
  • 악성코드 배포 (Malware Distribution): 사용자의 브라우저를 통해 악성코드를 다운로드하게 만들 수 있습니다.
  • 개인 정보 탈취 (Information Disclosure): 사용자의 개인 정보나 민감한 데이터를 탈취할 수 있습니다.
  • 웹사이트 변조 (Website Defacement): 웹 페이지의 내용을 임의로 변경하여 사용자에게 잘못된 정보를 보여줄 수 있습니다.

4. XSS 공격 방어 방법

XSS 공격을 방어하기 위한 몇 가지 주요 방법들은 다음과 같습니다.

4.1. 입력값 검증 및 필터링 (Input Validation and Filtering)

사용자로부터 입력받는 모든 데이터에 대해 신뢰할 수 없는 문자를 제거하거나, 허용된 문자만 받도록 엄격하게 검증해야 합니다. HTML 태그나 스크립트 코드로 해석될 수 있는 특수 문자(예: <, >, ', ", &)를 필터링하는 것이 중요합니다.

이 이미지는 입력값 검증 및 필터링의 중요성을 보여줍니다.

  • Before: Vulnerable: 필터링 없이 사용자의 입력( <script>alert('XSS')</script>)이 그대로 웹 페이지에 반영되면, 공격에 취약해집니다.
  • After: Secure: 입력된 데이터에 대해 필터링(Filter/Sanitize)을 수행하여 특수 문자(예: <를 &lt;로, >를 &gt;로)를 이스케이프 처리하면, 악성 스크립트가 실행되지 않고 일반 텍스트로 표시되어 안전해집니다.

4.2. 출력 인코딩 (Output Encoding)

사용자로부터 입력받은 데이터를 웹 페이지에 출력하기 전에는 항상 적절한 컨텍스트에 맞춰 인코딩해야 합니다. HTML 컨텍스트에서는 HTML 엔티티 인코딩을, JavaScript 컨텍스트에서는 JavaScript 인코딩을 적용해야 합니다.

이 이미지는 출력 인코딩의 중요성을 나타냅니다.

  • Before: Vulnerable: 서버에서 악성 스크립트가 포함된 데이터를 필터링 없이 그대로 사용자에게 보내면, 브라우저에서 스크립트가 실행됩니다.
  • After: Secure: 서버에서 사용자에게 데이터를 보내기 전에 출력 인코딩(HTML Entity Encoding)을 적용하면, 악성 스크립트가 일반 텍스트(&lt;script&gt;alert('XSS')&lt;/script&gt;)로 안전하게 표시되어 실행되지 않습니다.

4.3. CSP (Content Security Policy) 사용

CSP는 브라우저가 신뢰할 수 있는 소스에서만 스크립트나 기타 리소스를 로드하도록 지시하는 보안 메커니즘입니다. 이를 통해 악성 스크립트가 실행되는 것을 차단할 수 있습니다.

이 이미지는 CSP의 적용 전후를 비교합니다.

  • Before: Vulnerable: CSP가 없으면, 외부에서 주입된 스크립트를 포함하여 모든 스크립트가 실행될 수 있습니다.
  • After: Secure: 웹 서버가 Content-Security-Policy: default-src 'self';와 같은 CSP 헤더를 전송하면, 브라우저는 이 정책을 따르게 됩니다. 이 경우, self (동일 출처)에서만 스크립트 로드를 허용하므로, 외부에서 주입된 악성 스크립트의 실행이 차단됩니다.

4.4. HttpOnly 플래그 사용

세션 쿠키에 HttpOnly 플래그를 설정하면, JavaScript에서 해당 쿠키에 접근하는 것을 방지할 수 있습니다. 이는 XSS 공격을 통해 쿠키가 탈취되는 것을 막는 데 효과적입니다.

이 이미지는 HttpOnly 플래그의 중요성을 보여줍니다.

  • Before: Vulnerable: HttpOnly 플래그가 설정되지 않은 쿠키는 악성 스크립트( <script>document.cookie</script>)를 통해 쉽게 접근되어 탈취될 수 있습니다.
  • After: Secure: 서버가 Set-Cookie: sessionid=...; HttpOnly와 같이 HttpOnly 플래그를 설정하여 쿠키를 전송하면, 클라이언트 측 JavaScript는 해당 쿠키에 접근할 수 없게 됩니다. 이로 인해 XSS 공격이 발생하더라도 세션 쿠키 탈취는 방지됩니다.

5. XSS 공격 실습 (Google Colab Python 코드)

이제 간단한 파이썬 Flask 애플리케이션을 통해 XSS 공격을 시뮬레이션하고 방어하는 도식을 만들어보겠습니다. 이 코드는 Google Colab에서 직접 실행해볼 수 있습니다.

5.1. XSS 취약점 시뮬레이션 코드 (Vulnerable)

먼저, XSS에 취약한 간단한 웹 애플리케이션을 만들어보겠습니다. 이 애플리케이션은 사용자 입력을 필터링하지 않고 그대로 페이지에 출력합니다.

# Google Colab에서 Flask 애플리케이션 실행을 위한 라이브러리 설치
!pip install Flask ngrok pyngrok

from flask import Flask, request, escape
from pyngrok import ngrok
import threading
import time

# Flask 앱 인스턴스 생성
app = Flask(__name__)

# Ngrok 설정 (Ngrok 인증 토큰이 필요합니다. https://ngrok.com/signup 에서 발급받으세요)
# YOUR_NGROK_AUTH_TOKEN 대신 실제 토큰을 입력하세요.
# ngrok.set_auth_token("YOUR_NGROK_AUTH_TOKEN") 

# XSS 취약한 검색 페이지
@app.route('/vulnerable_search')
def vulnerable_search():
    query = request.args.get('query', '') # 사용자 입력을 그대로 받음
    html_response = f"""
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Vulnerable Search</title>
        <style>
            body {{ font-family: Arial, sans-serif; margin: 20px; }}
            .container {{ max-width: 800px; margin: auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; box-shadow: 2px 2px 10px rgba(0,0,0,0.1); }}
            h1 {{ color: #333; }}
            p {{ color: #666; }}
            .result {{ background-color: #f9f9f9; padding: 10px; border: 1px dashed #ccc; margin-top: 15px; }}
            .warning {{ color: red; font-weight: bold; }}
        </style>
    </head>
    <body>
        <div class="container">
            <h1>Vulnerable Search Page</h1>
            <p>You searched for: <span class="warning">{query}</span></p>
            <div class="result">
                <p>No results found for your query. Try something else!</p>
            </div>
            <hr>
            <p>Try searching for: <code>&lt;script&gt;alert('XSS Attack!');&lt;/script&gt;</code></p>
        </div>
    </body>
    </html>
    """
    return html_response

# Flask 앱 백그라운드에서 실행
def run_flask_app():
    app.run(port=5000)

# Ngrok 터널 시작 및 URL 출력
def start_ngrok():
    # Ngrok 터널이 이미 실행 중인지 확인하고 종료 (다중 실행 방지)
    try:
        if ngrok.get_tunnels():
            print("Existing Ngrok tunnels found. Closing them...")
            ngrok.disconnect()
            time.sleep(1) # 잠시 대기
    except Exception as e:
        print(f"Error checking/closing ngrok tunnels: {e}")

    # 새로운 Ngrok 터널 시작
    public_url = ngrok.connect(5000)
    print(f"Ngrok Tunnel URL: {public_url}/vulnerable_search")

# Flask 앱 스레드 시작
flask_thread = threading.Thread(target=run_flask_app)
flask_thread.start()

# Ngrok 터널 시작
start_ngrok()

# Colab 셀이 계속 실행되도록 유지
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print("Stopping Flask and Ngrok...")
    ngrok.disconnect()
    flask_thread.join(timeout=1) # 스레드 종료 대기 (타임아웃 설정)
    print("Server stopped.")

 
실행 방법:

  1. 위 코드를 Google Colab 셀에 붙여넣고 실행합니다.
  2. YOUR_NGROK_AUTH_TOKEN 부분에 실제 ngrok 인증 토큰을 입력하거나, 토큰 없이 일시적으로 사용하려면 해당 라인을 주석 처리합니다.
  3. 실행하면 Ngrok Tunnel URL이 출력됩니다. 이 URL을 복사하여 웹 브라우저에서 엽니다.
  4. URL 뒤에 ?query=<script>alert('XSS Attack!');</script> 를 추가하여 접속하면, 브라우저에서 경고창이 뜨는 것을 확인할 수 있습니다. 이는 악성 스크립트가 성공적으로 실행되었음을 의미합니다.

5.2. XSS 방어 코드 (Secure)

이제 escape() 함수를 사용하여 사용자 입력을 안전하게 처리하는 코드를 만들어보겠습니다. escape() 함수는 특수 문자( <, >, &, ', ")를 HTML 엔티티로 변환하여 스크립트가 실행되지 않도록 막아줍니다.

# Google Colab에서 Flask 애플리케이션 실행을 위한 라이브러리 설치
!pip install Flask ngrok pyngrok

from flask import Flask, request, escape
from pyngrok import ngrok
import threading
import time

# Flask 앱 인스턴스 생성
app = Flask(__name__)

# Ngrok 설정 (Ngrok 인증 토큰이 필요합니다. https://ngrok.com/signup 에서 발급받으세요)
# YOUR_NGROK_AUTH_TOKEN 대신 실제 토큰을 입력하세요.
# ngrok.set_auth_token("YOUR_NGROK_AUTH_TOKEN")

# XSS 방어된 검색 페이지
@app.route('/secure_search')
def secure_search():
    query = request.args.get('query', '')
    safe_query = escape(query) # escape() 함수로 사용자 입력을 안전하게 처리
    html_response = f"""
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Secure Search</title>
        <style>
            body {{ font-family: Arial, sans-serif; margin: 20px; }}
            .container {{ max-width: 800px; margin: auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; box-shadow: 2px 2px 10px rgba(0,0,0,0.1); }}
            h1 {{ color: #333; }}
            p {{ color: #666; }}
            .result {{ background-color: #f9f9f9; padding: 10px; border: 1px dashed #ccc; margin-top: 15px; }}
            .safe {{ color: green; font-weight: bold; }}
        </style>
    </head>
    <body>
        <div class="container">
            <h1>Secure Search Page</h1>
            <p>You searched for: <span class="safe">{safe_query}</span></p>
            <div class="result">
                
                <p>No results found for your query. Your input was safely handled!</p>
            </div>
            <hr>
            <p>Try searching for: <code>&lt;script&gt;alert('XSS Attack!');&lt;/script&gt;</code></p>
        </div>
    </body>
    </html>
    """
    return html_response

# Flask 앱 백그라운드에서 실행
def run_flask_app():
    app.run(port=5000)

# Ngrok 터널 시작 및 URL 출력
def start_ngrok():
    try:
        if ngrok.get_tunnels():
            print("Existing Ngrok tunnels found. Closing them...")
            ngrok.disconnect()
            time.sleep(1)
    except Exception as e:
        print(f"Error checking/closing ngrok tunnels: {e}")

    public_url = ngrok.connect(5000)
    print(f"Ngrok Tunnel URL: {public_url}/secure_search")

# Flask 앱 스레드 시작
flask_thread = threading.Thread(target=run_flask_app)
flask_thread.start()

# Colab 셀이 계속 실행되도록 유지
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print("Stopping Flask and Ngrok...")
    ngrok.disconnect()
    flask_thread.join(timeout=1)
    print("Server stopped.")
 

'보안' 카테고리의 다른 글

IPSec  (0) 2025.09.20
크로스 사이트 요청 위조 (Cross-Site Request Forgery, CSRF)  (0) 2025.09.03
SQL 인젝션 (Injection)  (0) 2025.09.02
Pharming  (1) 2025.09.02
SSL/TLS (Secure Sockets Layer/Transport Layer Security)  (2) 2025.09.02