XSS 취약점의 개념과 발생 원인을 알아보고 Stored XSS, Reflected XSS, DOM Based XSS의 동작 방식과 차이점, 안전한 대응 방법을 설명합니다.
웹사이트는 사용자가 입력한 검색어, 게시물, 댓글, 닉네임과 같은 정보를 화면에 출력합니다. 이때 외부 입력값을 안전하게 처리하지 않으면 공격자가 삽입한 스크립트가 다른 사용자의 웹 브라우저에서 실행될 수 있습니다. 이러한 웹 보안 취약점을 XSS(Cross-Site Scripting)라고 합니다.
XSS 공격에 성공하면 세션 정보 탈취, 사용자 입력정보 노출, 악성 페이지 이동, 화면 변조와 같은 피해가 발생할 수 있습니다. 특히 웹사이트의 정상적인 주소와 화면을 통해 스크립트가 실행되기 때문에 사용자가 공격 여부를 쉽게 알아차리기 어렵습니다.
XSS는 스크립트가 저장되고 전달되는 방식에 따라 일반적으로 Stored XSS, Reflected XSS, DOM Based XSS로 구분합니다.
XSS 취약점이란?
XSS는 신뢰할 수 없는 외부 입력값이 웹페이지에 안전하지 않은 형태로 출력돼 브라우저에서 코드로 실행되는 취약점입니다.
웹 애플리케이션이 사용자 입력값을 단순한 문자 데이터로 출력한다면 문제가 발생하지 않습니다. 그러나 입력값을 HTML이나 자바스크립트 문법으로 해석할 수 있는 위치에 그대로 삽입하면 브라우저가 해당 값을 명령으로 실행할 수 있습니다.
예를 들어 검색 결과 화면에서 다음과 같이 입력값을 HTML 영역에 직접 출력한다고 가정하겠습니다.
<div id="result">사용자가 입력한 검색어</div>
서버 또는 클라이언트 스크립트가 검색어를 별도의 처리 없이 innerHTML에 삽입하면 입력값이 일반 문자열이 아니라 HTML 요소로 해석될 수 있습니다.
XSS 취약점의 핵심은 외부 입력값이 브라우저에서 실행 가능한 코드로 해석되는 것입니다.
XSS가 발생하는 주요 원인
XSS는 단순히 <script> 태그를 허용했기 때문에 발생하는 것만은 아닙니다. 이벤트 속성, URL, HTML 속성, 자바스크립트 문자열 등 다양한 위치에서 발생할 수 있습니다.
대표적인 발생 원인은 다음과 같습니다.
- 사용자 입력값을 HTML 화면에 그대로 출력하는 경우
- 출력 위치에 맞는 인코딩을 적용하지 않은 경우
innerHTML,outerHTML,document.write()를 안전하지 않게 사용하는 경우- 입력값을 자바스크립트 코드나 이벤트 속성에 삽입하는 경우
- URL 파라미터나 해시 값을 검증 없이 화면에 출력하는 경우
- HTML 입력이 필요한 기능에서 검증된 Sanitizer를 사용하지 않는 경우
- 오래된 라이브러리나 프레임워크를 사용하는 경우
입력 단계에서 특정 문자열만 제거하는 방식은 우회될 가능성이 있습니다. 따라서 XSS 대응에서는 입력값 검증뿐만 아니라 출력 위치에 맞는 인코딩과 안전한 DOM API 사용이 중요합니다.
Stored XSS란?
Stored XSS는 악성 입력값이 서버의 데이터베이스나 게시물 등에 저장된 후 다른 사용자에게 반복적으로 전달되는 방식입니다. 저장형 XSS 또는 영구형 XSS라고도 부릅니다.
댓글, 게시판, 사용자 프로필, 상품 후기, 문의사항과 같이 입력한 내용이 서버에 저장되고 여러 사용자에게 보여지는 기능에서 주로 발생합니다.
Stored XSS의 일반적인 동작 과정은 다음과 같습니다.
- 공격자가 게시물이나 댓글에 조작된 입력값을 등록합니다.
- 입력값이 서버의 데이터베이스에 저장됩니다.
- 다른 사용자가 해당 게시물이나 댓글을 조회합니다.
- 저장된 값이 안전한 처리 없이 웹페이지에 출력됩니다.
- 사용자의 브라우저에서 스크립트가 실행됩니다.
Stored XSS는 공격자가 특정 사용자에게 링크를 전달하지 않아도 게시물을 열람한 여러 사용자에게 영향을 줄 수 있습니다. 관리자 페이지에서 실행되면 높은 권한을 가진 계정의 세션이나 관리 기능이 악용될 가능성도 있으므로 세 가지 방식 중 피해 범위가 크게 나타날 수 있습니다.
Reflected XSS란?
Reflected XSS는 URL 파라미터나 입력값이 서버의 응답 페이지에 즉시 반영될 때 발생하는 방식입니다. 반사형 XSS 또는 비영구형 XSS라고도 합니다.
검색 기능을 예로 들면 사용자가 입력한 검색어가 서버에 저장되지 않더라도 결과 화면에 “검색한 단어”로 다시 출력될 수 있습니다. 서버가 이 값을 안전하게 처리하지 않으면 조작된 입력값이 응답 페이지에 포함될 수 있습니다.
Reflected XSS의 일반적인 동작 과정은 다음과 같습니다.
- 공격자가 조작된 파라미터를 포함한 URL을 만듭니다.
- 이메일, 게시물 또는 메시지 등을 통해 사용자에게 URL을 전달합니다.
- 사용자가 해당 링크에 접속합니다.
- 서버가 URL 파라미터를 응답 HTML에 그대로 반영합니다.
- 사용자의 브라우저에서 스크립트가 실행됩니다.
Reflected XSS는 입력값이 데이터베이스에 저장되지 않는다는 점에서 Stored XSS와 다릅니다. 일반적으로 사용자가 조작된 링크를 클릭하거나 특정 요청을 실행해야 공격이 성립합니다.
다만 정상적인 웹사이트 주소가 사용되기 때문에 사용자는 링크를 신뢰할 가능성이 있습니다. URL 단축 서비스나 인코딩된 파라미터가 함께 사용되면 조작 여부를 쉽게 확인하기 어려울 수 있습니다.
DOM Based XSS란?
DOM Based XSS는 서버가 아니라 브라우저에서 실행되는 자바스크립트가 외부 입력값을 안전하지 않게 처리할 때 발생하는 방식입니다. DOM은 브라우저가 HTML 문서를 객체 형태로 표현하고 조작할 수 있도록 제공하는 구조입니다.
클라이언트 자바스크립트는 다음과 같은 위치에서 값을 읽을 수 있습니다.
location.searchlocation.hashdocument.URLdocument.referrerwindow.name- 웹 스토리지에 저장된 값
이처럼 외부 입력값을 가져오는 위치를 Source라고 합니다. 가져온 값을 innerHTML, document.write(), eval()처럼 코드 실행이나 HTML 해석으로 이어질 수 있는 위치에 전달하면 DOM Based XSS가 발생할 수 있습니다. 이러한 출력 위치를 Sink라고 합니다.
다음은 안전하지 않은 DOM 처리 방식의 예입니다.
const keyword = location.hash.substring(1);
document.getElementById("result").innerHTML = keyword;
위 코드는 URL의 해시 값을 읽어 innerHTML에 삽입합니다. 브라우저가 입력값을 HTML로 해석할 수 있으므로 보안 문제가 발생할 수 있습니다.
문자열만 출력하면 되는 기능에서는 다음과 같이 textContent를 사용하는 것이 안전합니다.
const keyword = location.hash.substring(1);
document.getElementById("result").textContent = keyword;
textContent는 전달된 값을 HTML 코드가 아닌 일반 문자로 처리합니다. 따라서 화면에 단순한 텍스트를 표시하는 기능이라면 innerHTML보다 textContent를 우선 사용하는 것이 좋습니다.
Stored·Reflected·DOM XSS 차이점
| 구분 | Stored XSS | Reflected XSS | DOM Based XSS |
|---|---|---|---|
| 주요 발생 위치 | 서버에 저장된 데이터 출력 | 서버 응답에 입력값 반영 | 브라우저의 자바스크립트 처리 |
| 데이터 저장 여부 | 데이터베이스 등에 저장 | 일반적으로 저장되지 않음 | 저장 여부와 직접적인 관련 없음 |
| 전달 방식 | 게시물·댓글 등을 조회할 때 전달 | 조작된 URL이나 요청을 통해 전달 | URL·해시 등 클라이언트 입력을 DOM에 반영 |
| 사용자 행동 | 취약한 페이지 열람 | 조작된 링크 접속 등이 필요 | 취약한 클라이언트 기능 실행 |
| 영향 범위 | 여러 사용자에게 반복적으로 영향 | 링크에 접근한 사용자에게 영향 | 취약한 스크립트를 실행한 사용자에게 영향 |
| 주요 점검 대상 | 게시판·댓글·프로필 | 검색·오류 메시지·URL 파라미터 | Source와 Sink 사이의 데이터 흐름 |
Stored와 Reflected는 주로 입력값이 서버 응답에 포함되는 방식으로 구분합니다. 반면 DOM Based XSS는 브라우저 자바스크립트 내부에서 입력값이 처리되는 과정에 초점을 맞춥니다.
따라서 실제 환경에서는 DOM 방식이 Stored 또는 Reflected 특성과 함께 나타날 수도 있습니다. 세 가지 용어를 완전히 배타적인 분류로 보기보다는 입력값이 어디에서 전달되고 어느 위치에서 실행되는지를 기준으로 분석해야 합니다.
XSS 취약점 대응 방법
출력 위치에 맞는 인코딩 적용
HTML 본문, HTML 속성, URL, 자바스크립트 문자열은 각각 해석 방식이 다릅니다. 따라서 값이 출력되는 위치에 맞는 인코딩을 적용해야 합니다.
HTML 본문에 출력할 값은 <, >, &, 따옴표와 같은 문자가 HTML 문법으로 해석되지 않도록 처리해야 합니다. 프레임워크가 제공하는 자동 이스케이프 기능을 임의로 해제하지 않는 것도 중요합니다.
안전한 DOM API 사용
단순한 문자열을 화면에 표시할 때는 innerHTML 대신 textContent 또는 innerText를 사용하는 것이 좋습니다. 새로운 HTML 요소가 필요하다면 createElement()로 요소를 생성하고 필요한 값은 안전한 속성으로 설정해야 합니다.
eval(), document.write(), 문자열 형태의 setTimeout()처럼 입력값이 코드 실행으로 연결될 수 있는 기능은 사용을 피해야 합니다.
HTML Sanitizer 적용
게시판 편집기처럼 사용자가 일부 HTML을 입력해야 하는 기능에서는 모든 태그를 단순히 제거하기 어렵습니다. 이러한 경우 검증된 Sanitizer 라이브러리를 사용해 허용된 태그와 속성만 남기는 방식으로 처리해야 합니다.
직접 제작한 정규표현식만으로 HTML 전체 구조를 안전하게 검사하기는 어렵습니다. 검증된 라이브러리를 최신 버전으로 유지하면서 사용하는 것이 좋습니다.
Content Security Policy 설정
CSP(Content Security Policy)는 브라우저가 실행할 수 있는 스크립트의 출처와 방식을 제한하는 보안 정책입니다. 올바르게 설정하면 XSS가 존재하더라도 임의의 스크립트가 실행될 가능성을 줄일 수 있습니다.
하지만 CSP는 안전한 코딩을 대신하는 기능이 아닙니다. 출력 인코딩과 안전한 DOM 처리 방식을 우선 적용하고 CSP를 추가적인 방어 계층으로 사용해야 합니다.
쿠키 보안 속성 설정
세션 쿠키에 HttpOnly 속성을 적용하면 자바스크립트가 해당 쿠키 값에 직접 접근하는 것을 제한할 수 있습니다. Secure와 SameSite 속성도 함께 검토해야 합니다.
다만 HttpOnly를 설정해도 XSS 자체가 제거되는 것은 아닙니다. 공격자가 사용자의 브라우저에서 정상 요청을 대신 실행할 가능성은 남아 있으므로 근본적인 코드 수정이 필요합니다.
XSS 점검 시 확인할 항목
XSS 취약점을 점검할 때는 다음 항목을 확인해야 합니다.
- 사용자 입력값이 어떤 화면에 다시 출력되는가?
- 입력값이 데이터베이스에 저장된 후 다른 사용자에게 제공되는가?
- URL 파라미터가 서버의 응답 HTML에 포함되는가?
- 클라이언트 스크립트가 URL이나 해시 값을 읽는가?
- 외부 입력값이
innerHTML이나document.write()로 전달되는가? - 출력 위치에 적합한 인코딩이 적용됐는가?
- HTML 입력 기능에 검증된 Sanitizer가 적용됐는가?
- 템플릿 엔진의 자동 이스케이프 기능이 활성화돼 있는가?
- CSP와 쿠키 보안 속성이 적절하게 설정돼 있는가?
보안 점검과 테스트는 반드시 자신이 소유하거나 명시적으로 허가받은 시스템에서만 수행해야 합니다.
자주 묻는 질문
XSS와 CSRF는 어떤 차이가 있나요?
XSS는 공격자가 삽입한 스크립트가 사용자의 브라우저에서 실행되는 취약점입니다. CSRF는 로그인된 사용자의 권한을 이용해 사용자가 의도하지 않은 요청을 전송하게 만드는 공격입니다. 발생 원리와 대응 방법이 서로 다릅니다.
특수문자를 모두 제거하면 XSS를 막을 수 있나요?
특정 문자만 제거하는 방식은 출력 위치와 브라우저 해석 방식에 따라 우회될 수 있습니다. 출력 위치에 맞는 인코딩, 안전한 DOM API, HTML Sanitizer를 함께 적용해야 합니다.
React나 Vue를 사용하면 XSS가 발생하지 않나요?
현대적인 프론트엔드 프레임워크는 일반적인 데이터 출력을 기본적으로 이스케이프합니다. 하지만 HTML 직접 삽입 기능을 사용하거나 외부 입력값을 위험한 DOM API에 전달하면 프레임워크 환경에서도 XSS가 발생할 수 있습니다.
세 가지 XSS 중 어떤 방식이 가장 위험한가요?
환경에 따라 다르지만 Stored XSS는 한 번 저장된 입력값이 여러 사용자에게 반복적으로 전달될 수 있어 피해 범위가 커질 가능성이 있습니다. 관리자 화면에서 실행되는 경우에는 더 큰 피해로 이어질 수 있습니다.
마무리
XSS는 외부 입력값이 브라우저에서 실행 가능한 코드로 해석될 때 발생하는 대표적인 웹 취약점입니다.
Stored XSS는 서버에 저장된 값이 여러 사용자에게 전달되는 방식이고, Reflected XSS는 요청에 포함된 값이 서버 응답에 즉시 반영되는 방식입니다. DOM Based XSS는 브라우저 자바스크립트가 URL이나 해시 등의 값을 안전하지 않은 DOM 영역에 삽입할 때 발생합니다.
XSS를 예방하려면 출력 위치에 맞는 인코딩, 안전한 DOM API, 검증된 Sanitizer, CSP, 쿠키 보안 속성을 함께 적용해야 합니다. 특히 단순 문자열 출력에는 innerHTML 대신 textContent를 사용하고, 템플릿 엔진의 자동 이스케이프 기능을 유지하는 것이 중요합니다.
핵심 요약: Stored XSS는 저장된 값, Reflected XSS는 응답에 반영된 값, DOM Based XSS는 브라우저 내부에서 처리된 값으로 인해 발생하며, 가장 중요한 대응 방법은 출력값 인코딩과 안전한 DOM 처리입니다.
핵심 키워드: XSS 취약점, Stored XSS, Reflected XSS, DOM Based XSS, XSS 차이점, XSS 대응 방법
태그: 웹 보안, 웹 취약점, XSS, 크로스사이트 스크립팅, 시큐어 코딩