이번에는 DOM-based XSS에 대해 공부해봐야지
앞에 웹은 어떻게 동작?? 글 마지막 부분에서 <script>태그에 대해 알아보면서 끝냈는데
여기에 이어서 이야기를 시작해보자면,
javascript는 개발자가 작성, 실행은 브라우저가 라는 구조였는데
지금 공부할 DOM-based XSS는 이런 구조에서 발생 하는데,
개발자가 작성한 javascript가 공격자가 조작한 값을 위험하게 처리하면서 문제가 생기는 것!
그럼 일단, DOM에 대해서 알아보자
DOM
- Document Object Model
DOM Tree
브라우저는 HTML을 DOM Tree로 만든다
예를들어서
<html>
<body>
<h1>Hello</h1>
<button id="btn">누르기</button>
<div id="result"></div>
</body>
</html>
브라우저는 이 코드를 단순 문자열이 아니라
document
└── html
└── body
├── h1
│ └── "Hello"
├── button id="btn"
│ └── "누르기"
└── div id="result"
이렇게 내부적으로 트리 구조로 바꿔서 관리를 하고
이것이, DOM Tree 이다
정리하면, 브라우저가 HTML 문서를 JavaScript로 찾고, 읽고, 수정할 수 있게 만들어 둔 객체, 트리 구조라고 보면 되겠다
이렇게, 브라우저가 HTML을 DOM Tree로 만들어두면, JavaScript는 원하는 요소를 쉽게 찾을 수 있다,,
돌아가는 순서를 정리 해보면
HTML 원본 -> 브라우저가 DOM Tree 생성 -> JavaScript가 DOM Tree에서 요소 찾기 -> 요소의 내용, 속성, 스타일, 이벤트 등 수정 -> 화면에 반영
결론적으로, 웹페이지가 단순히 HTML 파일을 보여주는 것이 아니라, 브라우저가 HTML을 구조화하고 JavaScript가 그 구조를 계속 조작하면서 동작하는 것이다
DOM-based XSS에 대해 말하기에 앞서,
innerHTML, textContent에 대해서 알아보자
textContent : 입력값을 글자로 넣음
innerHTML : 입력값을 HTML로 해석해서 넣음
먼저,
textContent
result.textContent = "<b>Hello</b>";
textContent 같은 경우네는 입력값을 그냥 글자로 넣고,
따라서, 화면에는 그대로 <b>Hello</b>가 출력된다 (태그로 해석하지 않는 것이다)
반면,
innerHTML은
result.innerHTML = "<b>Hello</b>";
입력값을 HTML로 해석해서 넣고,
그래서 DOM은
<div id="result"><b>Hello</b></div>
이렇게 바뀌고 화면에 Hello가 굵게 보이게 된다
DOM-based XSS
-브라우저에서 실행되는 JavaScript가 사용자가 조작 가능한 값을 읽고, 그 값을 innerHTML같은 위험한 DOM조작 지점에 넣어서 발생하는 XSS
- 서버가 사용자 입력을 응답 HTML에 위험하게 넣어서 발생하는 stored, reflected xss와 다르게, 프로트엔트 JavaScript가 브라우저 안에서 위험하게 처리해서 발생하는 취약점이다
예를들어서
<!DOCTYPE html>
<html lang="ko">
<head>
<mete charset="UTF-8">
<title>DOM XSS Lab</title>
</head>
<body>
<h1>검색 결과</h1>
<div id="result"></div>
<script>
const keyword = location.hash.substring(1);
document.getElementById("result").innerHTML = keyword;
</script>
</body>
</html>
이런 페이지가 있고
만약, 정상적인 입력이 있다고 한다면
사용자가 아래의 주소로 접속했다면
http://maru.com/dom#hello
브라우저에서
const keyword = location.hash.substring(1); 이부분에 따라
#hello를 가져오고 substring(1)에 따라 #도 제거하고
document.getElementById("result").innerHTML = keyword;
이부분에 따라 id=result인 부분에 hello가 추가되고
<div id="result>hello</div>
이렇게 DOM이 바뀌면서 화면에는 hello가 출력된다
이 예시에서는 그냥 문자열 hello를 출력했기에, 큰문제가 없다
근데 만약, 여기에 공격적인 입력을 한다면?,,
예를들어서
공격자가 링크에 hello가 아닌
http://maru.com/dom#<img src=x onerror=alert(1)>
이런 값을 넣는다면
const keyword = location.hash.substring(1);
이 부분에 따라 JavaScript가
keyword = "<img src=x onerror=alert(1)>";
이렇게 읽을 것이고
그럼 이 값이 innerHTML에 들어가면서
document.getElementById("result").innerHTML = "<img src=x onerror=alert(1)>";
이렇게 되고,
innerHTML은 문자열을 HTML로 해석하니까 DOM은
<div id="result">
<img src=x onerror="alert(1)">
</div>
이렇게 조작될 것이고,
src=x는 존재하지 않는 이미지 주소로, 이미지 로딩이 실패하면서 onerror 이벤트가 실행될 것이다!
그래서 결국 브라우저에서 alert(1)이 뜰것이다,,
정리를 해보면,
피해자가 조작된 URL 클릭
↓
서버는 정상 HTML/JS를 응답
↓
브라우저가 JavaScript 실행
↓
JavaScript가 location.hash를 읽음
↓
innerHTML에 넣음
↓
브라우저가 HTML로 해석하면서 이벤트 실행
결론적으로, DOM-based XSS는 프론트엔드 JavaScript가 조작 가능한 값을 DOM에 위험하게 넣어서 발생한다!
그럼 이 공격을 할 공격자는 무엇을 보고 DOM XSS를 찾을까,,?
여기서 Source, Sink가 나오는데
Source
-> 공격자가 조작할 수 있는 입력 출처
-> DOM XSS에서 자주 보는 source는,,
location.hash
location.search
location.href
document.URL
document.referrer
localStorage
sessionStorage
postMessage event.data
Sink
-> 값이 들어갔을 때 HTML로 해석되거나 JavaScript 실행으로 이어질 수 있는 위험한 지점
-> 대표적인 sink는,,
innerHTML
outerHTML
document.write()
insertAdjacentHTML()
eval()
setTimeout("문자열")
setInterval("문자열")
setAttribute("onclick",...)
이렇게, Source와 Sink가 있고, 이 2개가 연결이 되면서 공격이 발생하는 것이다.
정리하면, 조작 가능한 값이 위험한 Sink로 흘러가는 가?가 핵심이다.
예시로 했던 걸 보면
const keyword = location.hash.substring(1); // Source
result.innerHTML = keyword; // Sink
요런식,,
그렇다고 sink 자체가 무조건 문제가 아니라
신뢰할 수 없는 사용자 입력을 위험한 sink에 그대로 넣는 게 위험한 것!(위 예시처럼)
실제로
result.innerHTML = "<b>공지사항</b>";
이런식으로 개발자가 직접 작성한 고정 문자열은 공격자가 변경할 수 없으니 상관없다
그래서 총 정리를 해 본다면
DOM-based XSS 분석의 핵심은
- 조작 가능한 값이가? (Source)
- JavaScript가 그 값을 읽는가?
- 그 값이 위험한 위치에 들어가는가? (Sink)
- HTML/JavaScript로 해석되는가?
- textContent, escape, sanitize 같은 안전처리가 있는가?
요렇게 정리해 볼 수 있고,
그래서, 결국 DOM-based XSS는
공격자가 넣은 문자열 자체보다, 프론트엔드 JavaScript가 그 문자열을 DOM에 어떤 방식으로 넣느냐가 핵심이다.
**번외
id
-> HTML에서 요소에 붙이는 고유한 이름표 같은
-> 원칙적으로 반복 x, 한 페이지 안에서 고유해야 한다
<div id="ina"></div>
<div id="maru"></div>
HTML에서 요렇게 쓰고
자바스크립트에서
document.getElemetById("maru")
이런식으로 id=maru인 요소를 찾는다
class
-> 여러 요소를 묶고 싶으면 class를 사용한다
-> 그룹 이름표,,
<p class="comment">댓글 1</p>
<p class="comment">댓글 2</p>
<p class="comment">댓글 3</p>
**
'web2' 카테고리의 다른 글
| docker 사용하기 (run, compose) (0) | 2026.06.17 |
|---|---|
| CSRF(Cross-Site Request Forgery) (0) | 2026.06.04 |
| 웹은 어떻게 동작하나요(HTML,JS,CSS,브라우저의 관계) (0) | 2026.05.31 |
| XSS (cross-site scripting) (0) | 2026.05.30 |
| Same Origin Policy (SOP) (0) | 2026.05.16 |