web2

DOM-based XSS

maru_ 2026. 6. 2. 01:18

이번에는 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