본문 바로가기

Knowledge

[Knowledge] DOM-Based XSS

반응형

들어가기 전

XSS 취약점 실습 중 툴팁(Tooltip) 영역에서 Javascript가 실행되는 상황을 발견했다.

테이블 본문에서는 스크립트가 이스케이프 처리되어 실행되지 않았지만, 툴팁(Tooltip) 영역에서는 이스케이프 처리가 적용되지 않아 스크립트가 실행되는 상황이었다.

특히 흥미로운 점은:

  • <script> 태그는 삽입되어도 실행되지 않음
  • <img> 태그는 정상적으로 실행됨

해당 현상이 필자는 DOM 형식으로 코드가 짜여있어서 그런 것이라고 추측했으며 의문점에 대해 정리했다.


1. DOM이란 무엇인가?

1.1 정의

DOM(Document Object Model)은 브라우저가 HTML을 해석해 트리 구조로 메모리에 구성한 문서 구조이다.

브라우저는 HTML 문서를 단순한 텍스트로 보지 않고, 노드(Node)들의 계층 구조로 구성된 객체 모델로 바꿔 놓는다.

(DOM 조작: Javascript를 통해 HTML 태그를 동적으로 삽입하거나 수정하는 행위이다.)

 

실제 웹 애플리케이션은 사용자 행동, 서버 응답, 조건에 따른 인터렉션에 따라 콘텐츠가 변해야 한다. 이런 동적 웹을 표현하기 위해 DOM을 이용한다. (동적 방식)

1.2 DOM의 기본 구조

예를 들어 다음과 같은 HTML 코드가 있다고 하자:

<div><b>Hello</b></div>

 

브라우저는 이를 다음과 같이 메모리에서 표현한다:

document
└── html
    └── body
        └── div
            └── b
                └── "Hello"

 

1.3 DOM 조작

자바스크립트를 사용하면 DOM을 자유롭게 조작할 수 있다.

예를 들어 다음 코드는 div 요소의 내용을 <p> Modified </p> 교체한다:

document.querySelector("div").innerHTML = "<p>Modified</p>";

이 코드는 div 요소의 내용을 동적으로 <p><Modified</p>로 바꾸는 DOM 조작이다.

즉, HTML 태그를 삽입할 수 있는 게 바로 DOM 조작이다.

 

예시:

<!DOCTYPE html>
<html>
	<head>
    	<title>DOM 조작 예제</title>
    </head>
   	<body>
    	<div>기존 내용</div>
      	
        <script>
        	document.querySelector("div").innerHTML = "<p>Modified</p>";
        </script>
    </body>
</html>

 

위 코드를 실행 시키게되면 아래와 같은 일이 일어난다.

실행 전 DOM 구조:

document
└── html
    └── body
        ├── div
        │   └── "기존 내용입니다"
        └── script

 

실행 후 DOM 구조:

<div>
  <p>Modified</p>
</div>

→ 트리 구조로 보면 아래와 같음
document
└── html
    └── body
        ├── div
        │   └── p
        │       └── "Modified"
        └── script

즉, div 요소의 내용이 완전히 교체되면서, 새로운 <p> 태그가 DOM에 동적으로 생성된다.


2. Hands-on Lab: <script> VS <img>

왜 <script>는 실행되지 않는가?

document.body.innerHTML = '<script>alert("test")<\/script>'; // 실행되지 않음
  • 위 코드처럼 자바스크립트로 <script> 태그를 삽입하더라도, 브라우저는 이를 단순 태그로 인식할 뿐 실행하지 않는다.
  • 이유는 다음과 같다:
    • 브라우저는 보안 정책상 innerHTML, createElement() 등 DOM 조작을 통해 삽입된 <script> 태그는 자동 실행하지 않음
    • 이는 DOM-based XSS 방지를 위한 기본 보안메커니즘

 

반면 <img>는 왜 실행되는가?

document.body.innerHTML = 'img src="x" onerror="alert(`XSS`)">';
  • <img> 태그가 삽입되자 브라우저는 이미지를 로딩하려 시도한다.
  • src="x"는 유효하지 않음 → 로딩 실해
  • 이때 onerror 이벤트가 실행되어 JavaSript가 실행됨
    • 브라우저는 <img>의 onerror를 HTML 파싱 중 자동으로 이벤트를 처리하는 구조를 가지고 있다.

 

  • 다음은 위의 내용을 시각적으로 확인할 수 있는 HTML코드이다.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>XSS 태그 실행 테스트</title>
</head>
<body>
  <h2>1. script 태그 테스트</h2>
  <div id="script-container"></div>

  <h2>2. img onerror 테스트</h2>
  <div id="img-container"></div>

  <script>
    // 1. script 태그 삽입
    const scriptDiv = document.getElementById('script-container');
    scriptDiv.innerHTML = '<script>alert("✅ script 태그 실행 안됨ript>';

    // 2. img 태그 삽입
    const imgDiv = document.getElementById('img-container');
    imgDiv.innerHTML = '<img src="x" onerror="alert(\'🚨 img 태그 실행됨\')">';
  </script>
</body>
</html>

 

  • 아래 코드는 툴팁 쪽에서 스크립트가 터지도록 만들어 봤다.
  • 툴팁 UI 요소는 종종 innerHTML을 사용해 사용자 입력을 삽입하는데, 이 경우 <img> 태그를 이용한 입력이 DOM에 삽입되면 XSS가 발생할 위험이 있다.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>XSS 테스트</title>
  <style>
    table, td {
      border: 1px solid black;
      border-collapse: collapse;
      padding: 10px;
    }
    #tooltip-box {
      position: absolute;
      background: yellow;
      border: 1px solid red;
      padding: 5px;
      display: none;
      z-index: 1000;
    }
  </style>
</head>
<body>
  <h2>1. 테이블 + script 태그 툴팁</h2>
  <table>
    <tr>
      <td title="<script>alert('script XSS')</script>">마우스를 올려보세요 (script)</td>
    </tr>
  </table>

  <h2>2. 테이블 + img 태그 툴팁</h2>
  <table>
    <tr>
      <td title='<img src="x" onerror="alert(`img XSS`)">'>마우스를 올려보세요 (img)</td>
    </tr>
  </table>

  <h2>3. JavaScript 기반 툴팁 테스트 (innerHTML 삽입)</h2>
  <div id="tooltip-target" style="border:1px solid gray; display:inline-block; padding:10px;"
       onmouseover="showTooltip(event)"
       onmouseout="hideTooltip()">
    마우스를 올려보세요 (innerHTML)
  </div>
  <div id="tooltip-box"></div>

  <script>
    // 테스트용 입력값 (택 1)
    const userInput = '<img src=x onerror="alert(`🚨 실행됨 img 태그`)">';
    //const userInput = '<script>alert("❌ 실행 안 됨 script 태그")<\/script>';

    function showTooltip(e) {
      const tooltip = document.getElementById('tooltip-box');
      tooltip.innerHTML = userInput;
      tooltip.style.left = e.clientX + 'px';
      tooltip.style.top = (e.clientY + 20) + 'px';
      tooltip.style.display = 'block';
    }

    function hideTooltip() {
      document.getElementById('tooltip-box').style.display = 'none';
    }
  </script>
</body>
</html>

 


3. 결론 및 대응 방법

textContent를 사용하면 브라우저는 입력값을 그냥 문자열로 처리하기 때문에 <img>, <script> 등의 태그가 실행되지 않는다.

document.getElementById('tooltip').textContent = userInput;

 

또는 테이블에서 이스케이프처리 했던 것처럼 툴팁 부분도 이스케이프 처리를 하는 것도 대응 방안이 될 수 있을 거 같다.

 

반응형