Studying Web Hacking (XSS)
XSS
XSS란?
XSS(Cross-Site Scripting)
- 공격자가 웹페이지에서 자신이 만든 스크립트나 실행 가능한 브라우저 동작을 끼워 넣어, 그 페이지를 보는 사용자의 브라우저에서 실행되게 만드는 취약점
- “서버가 털린다” 보단 사용자 세션/권한/화면/행동이 탈취 또는 조작된다
위험한 이유
- 로그인된 사용자의 권한으로 요청을 보낼 수 있음
- 페이지 내용을 바꿔 피싱 UI를 띄울 수 있음
- 민감한 화면 정보, 토큰, 사용자 입력을 가로챌 수 있음
- 관리자 페이지에 발생하면 피해가 훨씬 큼
성립 조건
3단계가 연결되면 생김
- 공격자가 제어 가능한 입력값이 들어옴
- 그 값이 적절히 인코딩/검증/정제되지 않는다
- 브라우저가 그 값을 “문자열”이 아니라 “HTML/속성/스크립트”로 해석하는 위치에 넣는다
→ XSS는 “사용자 입력이 위험한 컨텍스트로 들어가는 문제”
컨텍스트란
-
HTML 본문 컨텍스트
<div>USER_INPUT</div> -
HTML 속성 컨텍스트
<input value="USER_INPUT"> -
URL 컨텍스트
<a href="USER_INPUT">link</a> -
JavaScript 컨텍스트
<script> const data = "USER_INPUT"; </sciprt> -
DOM sink 컨텍스트
element.innerHTML = userInput;
대표적 종류
1. Reflected XSS
사용자 요청에 들어간 값이 바로 응답 HTML에 반사됨
예) 검색어, 에러 메시지, URL 파라미터
## 취약 예시 ##
<p>검색어: USER_INPUT<p/>
서버가 USER_INPUT을 그대로 넣으면 Reflected XSS
2. Stored XSS
공격 문자열이 DB 등에 저장됐다가 나중에 다른 사용자에게 보여질 때 실행
예) 게시글, 댓글, 닉네임, 관리자 메모
## 취약 예시 ##
<div class="comment">SUER_COMMENT</div>
댓글 저장 시 필터링/정제가 부실하면 Stored XSS
3. Dom-based XSS
서버 응답 자체보다, 브라우저의 JS 코드가 URL,입력값,postMessage 같은 값을 읽어서 DOM에 위험하게 넣을 때 발생합니다.
-
DOM : Document Object Model(문서 객체 모델)
웹페이지(HTML)를 컴퓨터가 이해하기 쉽게 구조로 바꿔놓은 것
<html> <body> <h1>안녕</h1> <p>내용</p> </body> </html>위 HTML을 브라우저는 아래 트리 구조와 같이 이해
html └── body ├── h1 ("안녕") └── p ("내용")이 구조 전체가 DOM
-
DOM은 JS가 웹페이지를 조작할 수 있게 해줌
예) 글자 바꾸기, 버튼 클릭하면 내용 변경, 팝업
document.querySelector("h1").textContent = "바뀐 제목";
-
## 취약 예시 ##
const q = new URLSearchParams(location.search).get("q");
document.getElementById("result").innerHTML = q;
서버가 아니라 브라우저 JS가 직접 취약
PoC 예시
-
HTML 삽입 여부 확인
<b>teset</b> -
이벤트 핸들러 실행 여부 확인
<img src=x onerror=alert(1)> -
DOM XSS 기본 확인
const payload = '<img src=x onerror=alert(1)>'; target.innerHTML = payload;
자주 보이는 위험 Sink
sink : 외부 입력값이 “실제로 실행되거나 반영되는 최종 지점”
[사용자 입력] -> (가공/처리) -> [sink]
sink는 위험이 실제로 터지는 지점
대표적인 sink
-
innerHTML: 요소 안쪽 HTML을 통째로 바꿔버림div.innerHTML = userInput;- 문자열을 HTML로 해석
<script>같은 것도 실행될 수 있음
-
outerHTML: 요소 자기 자신까지 포함해서 바꿔버림div.outerHTML = "<p>바뀜</p>";- innerHTML보다 더 강력
- 역시 HTML로 해석됨
-
insertAdjacentHTML: 특정 위치에 HTML 삽입div.insertAdjacentHTML("beforeend", userInput);- 문자열을 그대로 HTML로 넣음
- 위치만 다를 뿐
innerHTML과 동일하게 위험
-
document.write(): 페이지에 직접 HTML을 써버림document.write(userInput);- 옛날 방식이지만 여전히 위험
- HTML + 스크립트 실행 가능
-
eval(): 문자열을 JavaScript 코드로 실행eval(userInput);- 문자열 → 코드로 변환해서 실행
- XSS뿐 아니라 RCE까지 이어질 수 있음
-
new Function(): 문자열로 함수 생성 후 실행new Function(userInput);- eval과 거의 동일한 위험성
- 동적으로 코드 실행
-
문자열 기반
seTimeout(), setInterval()setTimeout("alert(1)", 1000);- 문자열을 코드처럼 실행함
- 함수 대신 문자열 쓰면 위험
안전한 방식
setTimeout(() => alert(1), 1000);
예)
취약한 코드
box.innerHTML = userInpurt;
안전한 방식
box.textContent = userInput;
또는
const el = document.createElement("div"):
el.textContent = userInput;
box.appendChild(el);
→ 실무 감각으로
- HTML로 넣지 말고 text로 넣어라
- 문자열을 실행하지 말고 DOM API로 조립해라
방어 방법
- 출력 시 컨텍스트 맞춤 인코딩
- HTML 본문: HTML entity encoding
- 속성 : 속성 컨텍스트에 맞는 인코딩 + 반드시 따옴표 사용
- URL: URL 검증 + 허용 스킴만 허용(
http,https) - JS 문자열: JS 컨텍스트 인코딩
- CSS/스크립트 컨텍스트 직접 삽입은 가능하면 피하기
- 안전한 DOM API 사용
tesxtContentcreateElementsetAttributeappendChild
-
HTML Sanitization
사용작 HTML 입력을 정말 허용해야 할 때만 sanitizer를 씀
OWASP는 대표적으로
DOMPurify사용을 권장단, sanitizer도 지속 업데이트가 중요
- 프레임워크의 위험 escape hatch 주의
- React:
dangerouslySetInnerHTML - Angular:
bypassSecurityTrustAs* - Lit:
unsafeHTML
- React:
-
CSP는 보조 수단
CSP는 좋지만 주 방어책이 아님 (OWASP도 언급)
-
최신 방어 포인트: Trusted Types
특히 DOM XSS 방어에 유용
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-policy;이렇게 두면
innerHTML같은 위험 sink에 문자열을 바로 넣는 습관을 강하게 통제할 수 있음
헷갈리기 쉬운 포인트
- 입력 검증만으로 XSS 막을 수 없음
- “
<script>차단 = XSS 방어 완료”가 아님 - 서버가 안전해 보여도 클라이언트 JS 때문에 DOM XSS가 날 수 있음
- 템플릿 엔진 auto-escape가 있어도 속성/URL/JS 컨텍스트는 별도 사고가 남
- sanitizer를 썼더라도, 이후 문자열을 다시 조작하면 방어가 무력화될 수 있음
실전 분석 순서
- 입력 지점 찾기
- URL 파라미터
- 폼 입력
- 저장되는 게시물/댓글
- 해시(
#) postMessage- 로컬 스토리지/세션 스토리지
- 반영 위치 확인
- HTML 본문인가
- 속성인가
- JS 문자열인가
- DOM API인가
- sink 확인
innerHTML계열인지- 실행함수(
eval) 계열인지 - URL 이동/삽입인지
- 방어 확인
- 인코딩이 컨텍스트에 맞는지
- sanitizer를 쓰는지
- Trusted Types/CSP가 있는지
- 프레임워크 escape hatch를 쓰는지
요즘 실무에서 특히 중요한 XSS 포인트
- 단순히
<script>...</script>만 막는다고 끝나지 않음 - 요즘은 DOM XSS, 프레임워크의 escape hatch, sanitizer 우회, 위험한 sink 사용이 더 중요
- OWASP는 React의
dangerouslySetInnerHTML, Angular의bypassSecurityTrustAs*, Lit의unsageHTML같은 지점을 대표 위험 지점으로 봄 - MDN 기준으로 Trusted Types 관련 CSP(require-trusted-types-for, trusted-types)는 2026년 2월부터 최신 브라우저군에서 Baseline 2026으로 표시되고 있어, 최신 프론트엔드 방어에서 중요성이 더 커짐. 다만 구형 브라우저는 별도 고려가 필요
-
OWASP는 CSP/WAF만으로 XSS를 막으려 하지 말고, 컨텍스트별 출력 인코딩 + 안전한 DOM API + HTML Sanitization을 기본으로 하라고 권장
-
CSP (Content Security Policy)
웹사이트에서 “어떤 코드만 실행해도 되는지” 정해놓는 보안 규칙
= 출입 가능한 코드만을 미리 정해놓는 출입통제 시스템
<script src="https://evil.com/hack.js"></script>다음과 같은 XSS 스크립트 삽입 공격이 있을 때 CSP가 있으면
evil.com을 차단한다.Trusted Types
위험한 HTML/스크립트를 아무 문자열로 넣지 못하게 막는 규칙
element.innerHTML = userInput;만약 이 JS 코드에서 userInput이 아래와 같으면
<script>alert(1)</script>바로 XSS 발생!!