코드 리뷰에서 innerHTML의 보안 취약점에 대해 피드백을 받고 찾아본 내용이다.
XSS란?
XSS(Cross-site scripting)는 공격자가 클라이언트 측에서 웹 사이트에 악성 코드를 삽입하는 공격 기법을 의미한다. 개발자가 본인의 마크업을 사이트에 삽입하는 경우엔 사실 걱정할 필요가 거의 없다. 위험은 사용자의 입력 혹은 제 3자가 출처인 컨텐츠를 화면에 출력할 때 발생한다. 이 경우 innerHTML
과 insertAdjacentHTML
이 문제가 된다.
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
사용- 텍스트 내용을 설정하기 위해서는
textContent
,innerText
를 사용하는 것이 좋다. textContent와 innerText의 차이점
- 텍스트 내용을 설정하기 위해서는
-
DOM에 삽입 전 sanitizing
- 백엔드에서 XSS 필터링
-
직접 요소 생성, 조작
createElement
…
현실적으로 innerHTML
과 insertAdjacentHTML
을 안 쓸 수는 없다고 생각한다. 사용자 혹은 제 3자가 출처인 컨텐츠는 textContent
로 출력하고 DOM 조작 시 클라이언트에서 인코딩 후 서버단에서 필터링을 거치는 것이 좋아보인다.