innerHTML, insertAdjacentHTML과 XSS 공격

February 18, 2022

코드 리뷰에서 innerHTML의 보안 취약점에 대해 피드백을 받고 찾아본 내용이다.

XSS란?

XSS(Cross-site scripting)는 공격자가 클라이언트 측에서 웹 사이트에 악성 코드를 삽입하는 공격 기법을 의미한다. 개발자가 본인의 마크업을 사이트에 삽입하는 경우엔 사실 걱정할 필요가 거의 없다. 위험은 사용자의 입력 혹은 제 3자가 출처인 컨텐츠를 화면에 출력할 때 발생한다. 이 경우 innerHTMLinsertAdjacentHTML이 문제가 된다.

innerHTML, insertAdjacentHTML의 보안 취약점

<input id='innerhtml-input'></input>
<button id='innerhtml-input-btn'>innerHTML submit</button>

<input id='insertadjacenthtml-input'></input>
<button id='insertadjacenthtml-input-btn'>insertAdjacent submit</button>

<div class='submitted-input'></div>

<script>
    // innerHTML로 텍스트 삽입
    document.querySelector('#innerhtml-input-btn').addEventListener("click", (e) => {
      const userInput = document.querySelector('#innerhtml-input').value
      document.querySelector('.submitted-input').innerHTML = userInput
    })

    // insertAdjacentHTML로 텍스트 삽입
    document.querySelector('#insertadjacenthtml-input-btn').addEventListener("click", (e) => {
      const userInput = document.querySelector('#insertadjacenthtml-input').value
      document.querySelector('.submitted-input').insertAdjacentHTML('beforeend', userInput)
    })
</script>

innerHTML은 특정 HTML 요소의 마크업을 갈아 끼울 수 있어 편리하다. 하지만 그만큼 XSS에 대해 취약하다. 위의 코드<img src onerror="alert('evil')">을 입력하고 제출 버튼을 클릭해보자. 웹사이트가 사용자의 입력에 포함된 alert('evil')을 실행하는 것을 알 수 있다.

insertAdjacentHTML() 메서드는 HTML or XML 같은 특정 텍스트를 파싱하고, 특정 위치에 DOM tree 안에 원하는 node들을 추가 한다.  이미 사용중인 elemet 는 다시 파싱하지 않는다. 그러므로 element 안에 존재하는 element를 건드리지 않는다. (innerHtml은 과 좀 다름). innerHtml보다 작업이 덜 드므로 빠르다.

insertAdjacentHTML은 innerHTML에 비해 성능상의 이점이 있어 자주 쓰이지만 마찬가지로 텍스트의 마크업을 파싱해 DOM에 노드들을 추가하므로 XSS에 취약하다.

XSS 공격을 방지하기 위해선

  • 사용자 혹은 제 3자의 텍스트에는 textContent 사용

  • DOM에 삽입 전 sanitizing

  • 백엔드에서 XSS 필터링
  • 직접 요소 생성, 조작

    • createElement

현실적으로 innerHTMLinsertAdjacentHTML을 안 쓸 수는 없다고 생각한다. 사용자 혹은 제 3자가 출처인 컨텐츠는 textContent로 출력하고 DOM 조작 시 클라이언트에서 인코딩 후 서버단에서 필터링을 거치는 것이 좋아보인다.


우정민

웹 개발, 프론트엔드