🔥 크로스 사이트 스크립팅(XSS) 완전 이해하기
크로스 사이트 스크립팅(Cross-Site Scripting, XSS)은
공격자가 신뢰할 수 있는 웹사이트에 악성 스크립트를 삽입하고, 이를 다른 사용자의 브라우저에서 실행시키는 웹 보안 취약점입니다.
이를 통해 공격자는 다음과 같은 행동을 할 수 있습니다:
- 사용자의 세션 쿠키 탈취
- 계정 권한을 가로채거나 개인 정보 수집
- 웹 페이지 내용을 변경
- 악성 사이트로 자동 리디렉션
등, 사용자를 속에서 보이지 않게 공격할 수 있는 매우 위험한 방식입니다.
🔎 XSS 공격의 종류
XSS 공격은 모두 “브라우저에서 악성 JavaScript를 실행한다”는 공통 목표가 있습니다.
하지만 그 스크립트가 어디에서 기원하는지에 따라 종류가 달라집니다.
아래 세 가지가 가장 대표적입니다 👇
1️⃣ 저장형(Persistent / Stored) XSS
악성 스크립트가 웹사이트의 데이터베이스에 저장되었다가, 이후 여러 사용자에게 전달되는 공격입니다.
예:
해커가 게시판 댓글에 <script>...</script> 를 남김 →
DB에 저장 →
그 페이지를 보는 모든 사용자의 브라우저에서 실행됨.
특징: 가장 위험하고 지속적이며, 피해자가 많을수록 파급력이 커짐.
2️⃣ 반사형(Reflected) XSS
악성 스크립트가 사용자의 요청(Request) 에 포함되었다가,
서버가 이를 그대로 페이지에 반사(reflect)하여 응답할 때 발생하는 유형입니다.
예:
악성 링크 클릭 →
URL 파라미터에 포함된 스크립트가 그대로 페이지에 반영 →
피해자의 브라우저에서 바로 실행.
특징
- 한 번만 실행되는 일회성 공격
- 대부분 피싱 형태의 링크 클릭을 유도하여 발생
- 검색창, 에러 메시지, 로그인 페이지 등에서 자주 발견됨
3️⃣ DOM 기반(DOM-based) XSS
악성 스크립트 취약점이 서버가 아닌 클라이언트(JavaScript) 에 존재할 때 발생합니다.
예:
프론트엔드 코드에서 innerHTML 등을 부주의하게 사용 →
URL 해시값이나 입력값을 클라이언트에서 직접 DOM에 삽입 →
스크립트가 그대로 실행
특징
- 서버는 안전하더라도, 프론트엔드 코드가 취약하면 발생
- SPA(React, Vue, Angular 등) 환경에서 더 흔함
- 완전히 클라이언트 사이드에서만 일어나는 XSS
1. 저장(Persistent) XSS

공격자가 게시물로 악성코드 <script>...</script> 를 등록해 두면 유저가 웹페이지를 열람할 경우 웹서버는 악성코드를 포함한 html 을 작성하여 유저에 전송 -> 자동적으로 유저의 정보(쿠키=세션ID)를 공격자가 준비해둔 API로 자동 전송.
2. 반사(Reflected) XSS

유저가 공격자의 서버에 접속(혹은 이메일등의 링크 클릭)을 하면 공격자의 의도대로 XSS 대책이 안 되어있는 검색 서버로 악성코드의 검색 문자열을 가지고 자동 이동시키면 검색 서버는 결과 페이지에 악성코드를 html에 담아서 리턴하게 된다. (보통 검색 페이지는 제일 윗 부분에 유저가 검색한 키워드를 표시한다.)
그러면 유저 브라우저에서 악성 코드가 자동 실행되어 정보가 공격자가 준비한 서버로 전송되게 된다.
3. DOM 기반(DOM-based) XSS

서버가 문제인 1·2번과 달리, 3번은 “브라우저 안의 자바스크립트 코드 자체가 문제”여서 클라이언트 측에서 XSS가 발생하는 방식이다.
- 저장형/반사형 XSS: 서버가 이미 오염된 HTML을 만들어 돌려준다.
- DOM 기반 XSS: 서버 응답은 깨끗하지만, 브라우저에서 실행되는 자바스크립트가 사용자 입력을 잘못 처리하여 DOM을 통해 스크립트를 만든다.
XSS 예방책 정리
1. 인코딩(Encoding)은 기본 중의 기본
사용자 입력을 브라우저가 “코드”로 해석하지 않고, “텍스트”로만 처리하도록 변환하는 과정입니다.
- 상황: 게시판 댓글에 해커가 악성 스크립트를 입력함
- 해커의 입력:
<script>alert('해킹!');</script>
[방어 실패 예시] 입력을 그대로 출력하면 브라우저가 실행해 버립니다.
<div><script>alert('해킹!');</script></div>
[방어 성공 예시 - HTML 인코딩 적용] 특수 문자를 < 등 안전한 형태로 변경합니다.
<div><script>alert('해킹!');</script></div>
2. 인코딩만으로는 부족할 때 (유효성 검사 / Validation)
인코딩을 해도 위험한 경우가 있습니다. 대표적으로 링크(URL)입니다.
- 상황: 사용자가 홈페이지 주소를 입력하는 프로필 설정
- 해커 입력:
javascript:alert('쿠키탈취')
[문제점] HTML 속성(href) 안에서는 인코딩을 해도 javascript: 프로토콜이 그대로 실행됩니다.
<a href="javascript:alert('쿠키탈취')">내 홈페이지</a>
[방어 성공 - URL 화이트리스트 검사]
// 입력값이 http 또는 https로 시작하지 않으면 차단
if (userInput.match(/^https?:\/\//)) {
return userInput; // 안전하므로 허용
} else {
return "Error"; // 차단
}
3. 문맥(Context)에 따른 처리
데이터가 "어디에" 들어가느냐에 따라 방어 방법이 달라집니다.
- 상황: 사용자의 닉네임 Hong"GilDong을 JS 변수에 넣으려고 할 때
[실패 예시] HTML 인코딩을 자바스크립트에 그대로 쓰면 보안 문제가 발생합니다.
// 해커 입력: "; alert(1); //
// HTML 인코딩만 하면 JS 문법을 깨뜨릴 수 있음
var name = ""; alert(1); //";
[방어 성공 - JavaScript 문맥용 인코딩]
// 유니코드 이스케이프 활용하여 안전하게 변환됨
var name = "\u0022; alert(1); \/\/";
4. 클라이언트와 서버 양쪽에서 방어
한쪽만 막으면 구멍이 생깁니다.
서버 사이드(Server-Side) 방어
데이터베이스에 저장되기 전, 또는 저장된 데이터를 HTML로 출력할 때 인코딩 처리.
# (예: Python Flask)
# 템플릿 엔진이 자동으로 인코딩을 수행하여 HTML을 생성
return render_template('profile.html', name=user_input)
클라이언트 사이드(Client-Side) 방어
서버를 거치지 않고 DOM 조작으로 화면을 만들 때 필요합니다.
// 위험: innerHTML은 태그를 실행함
document.getElementById('msg').innerHTML = userInput;
// 안전: textContent는 텍스트로만 취급함
document.getElementById('msg').textContent = userInput;
5. 최후의 보루: CSP (Content Security Policy)
위 과정 중 실수가 있어 스크립트가 삽입되더라도, CSP가 실행 자체를 막아주는 안전장치입니다.
상황: 해커가 외부 악성 스크립트 파일을 삽입함
<script src="http://hacker.com/attack.js"></script>
CSP 설정 예시 (HTTP 헤더)
Content-Security-Policy: script-src 'self' https://trusted.com;
의미: 이 사이트는 'self'와 https://trusted.com에서 온 스크립트만 실행하고 나머지는 모두 차단.
결과: 해커의 attack.js는 허용 도메인이 아니므로 브라우저가 다운로드를 거부하고 실행을 막습니다.
'Web Security' 카테고리의 다른 글
| XSS 방지책 (Express.js) (0) | 2025.12.02 |
|---|---|
| XSS 방지책 (Flask) (0) | 2025.12.02 |
| XSS 방지책 (ReactJS) (0) | 2025.12.02 |
| CSRF (Cross-Site Request Forgery) attack (0) | 2025.12.02 |
| SQL Injection (SQLi) (0) | 2025.10.30 |