Element.innerHTML 자바스크립트 실행 취약점

Element.innerHTML 자바스크립트 실행 취약점

HTML 에서 Element.innerHTML 태그는 요소(Element) 내 포함된 HTML, XML 마크업 정보를 읽거나 쓸 때 사용된다.

일반적인 사용법은 다음과 같다.

const content = element.innerHTML; // element 의 HTML 값을 읽어 content 에 저장.

element.innerHTML = htmlString; // htmlString 의 값을 읽어 element 의 HTML 값으로 씀.

HTML5 에서는 설령 innerHTML<script> 태그를 사용하는 자바스크립트가 포함되어도 <script> 태그를 자동으로 제거하기 때문에 실행되지 않는다.

출처 : https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0
Note : script Elements inserted using innerHtml do not execute when they are inserted.

하지만 <script> 태그를 이용하지 않고 자바스크립트를 실행하는 방식은 작동하기 때문에 취약점으로 사용될 수 있다.

const name = "<img src='x' onerror='alert(1)'>"; // 이미지 로딩 실패 시 alert() 호출.
el.innerHTML = name;

모종의 방법으로 공격자가 웹사이트의 innerHTML 요소에 임의의 값을 쓸 수 있다면, 해당 페이지에서 자바스크립트를 실행하도록 유도할 수 있다.

Example

Github 에 해당 취약점을 테스트해볼 수 있는 Docker 파일을 업로드했다. 이를 이용하여 실제로 테스트 해보자. 아래 예제 테스트는 Docker, git 이 설치된 Linux OS 환경에서 진행했다. Docker 를 이용하기 싫다면 Flask 를 실행할 수 있는 환경을 구성한 후 deploy 폴더 내 app.py 를 실행해도 된다.

git 을 이용해 docker 파일 다운로드, 폴더로 이동한다.

$ sudo git clone https://github.com/Moonding/innerHTML-Vulnerability
$ cd innerHTML-Vulnerability

docker 명령어로 innerhtml_vul 이라는 이름의 이미지를 빌드한다.

$ sudo docker build -t innerhtml_vul .

해당 도커 컨테이너는 내부적으로 8000번 포트를 오픈하고 있으므로, 호스트 PC 의 적절한 포트에 바인딩해야 한다. 아래 명령어는 앞서 빌드한 innerhtml_vul 이미지를 이용하여 innerhtml_container 라는 이름의 컨테이너를 생성, 8000번 포트에 바인딩했다.

$ sudo docker run -d -p 8000:8000 --name innerhtml_container innerhtml_vul

정상적으로 컨테이너가 실행되었다면 브라우저를 이용하여 127.0.0.1:8000 포트로 접속할 수 있다.

해당 예제 서버는 index.html 에서 GET 방식으로 param 이라는 이름의 인자 값을 받아 script id 값을 가지는 <div> 태그에 값을 쓴다. index.html 내 Form 에 문자열을 입력하고 Submit 버튼을 클릭하면 param 에 입력 값이 전달된다.

# index.html
{% extends "base.html" %}
{% block title %}Index{% endblock %}

{% block head %}
  {{ super() }}
  <style type="text/css">
    .important { color: #336699; }
  </style>
{% endblock %}

{% block content %}
<div id='script'>This is script div</div>
    <script>var x=new URLSearchParams(location.search); document.getElementById('script').innerHTML = x.get('param');</script>
    
    <form action="/" method="get">
      <label for="param"></label>
      <input type="text" id="param" name="param" placeholder="Type Javascript Here"><br><br>
      <input type="submit" value="Submit">
    </form> 
{% endblock %}

Form 에 <script>alert(1);</script> 이라는 경고문 출력 스크립트를 입력하면 자바스크립트가 실행되지 않은 채 <div> 에 값만 입력된 것을 확인할 수 있다.

하지만 Form 에 <img src='x' onerror='alert(1)'> 을 입력하면, 이미지 로딩에 실패하면서 경고문이 실행되는 것을 확인할 수 있다.