브라우저는 서버로부터 자원을 요청하고 이를 받아서 랜더링 엔진에 의해서 파싱 과정을 거쳐 화면에 최종 랜더링을 하게 된다.
이때 서버로부터 받아온 HTML 파일을 파싱하는 과정에서 DOM을 구성하여, HTML의 노드들의 상속 관계를 트리 구조 형태로 만든다고 배웠다. DOM은 단순히 문서의 계층적 구조와 정보 뿐만 아니라, 이를 제어할 수 있는 프로퍼티와 메서드인 DOM API를 제공한다.
이를 통해서 DOM이 만들어지고 화면에 랜더링 된 이후, 자바스크립트 코드에 의해서 해당 DOM의 구조 또는 스타일을 동적으로 변동할 수 있다.
이번 글에서는 노드의 생성과 추가 관점에서 DOM을 조작하는 2가지 메서드를 살펴보고 2가지 메서드의 장단점과 이를 보완하기 위한 방법을 살펴본다.
노드 프로토타입 체인 중 Element 프로토타입 객체의 메서드로, getter와 setter로 구성된 접근자 프로퍼티이다.
접근자 프로퍼티는 자체적인 값을 가지고 있지 않고, 객체 내에서 다른 프로퍼티의 값을 취득하거나 변경할 때 호출되는 getter/setter로 이뤄진 프로퍼티
getter를 호출하게 되면 해당 요소 노드의 콘텐츠 영역(시작 태그와 종료 태그 사이) 사이의 HTML 마크업을 모두 문자열로 변환한다.
<body>
<div class='container'> hello<span class='element'>Terry</span></div>
</body>
<script>
const $container = document.getElementByClassName('container')
console.log($container.innerHTML);
// 'hello<span class='element'>apple</span>'
</script>
이 때 innerHTML에 문자열을 할당하게 되면, 해당 요소 노드의 모든 자식 노드는 제거되고 해당 문자열이 HTML 마크업으로 파싱되어 요소노드의 자식노드로 생성된다.
이 방법은 굉장히 단순히 문자열로 DOM을 추가하거나 조작할 수 있다는 장점이 있다.
그러나 이렇게 사용자로부터 입력받은 데이터를 바로 innerHTML의 데이터로 전달하여 파싱하는 과정은 외부 공격(크로스 사이트 스크립팅 공격)에 취약할 수 있다.
innerHTML 이외의 Element.prototype.insertAdjacentHTML 메서드 역시 사용자로부터 입력받은 문자열을 HTML로 파싱하기 때문에, 동일한 취약점을 가지고 있다.
크로스 사이트 스크립팅은 웹 어플리케이션에서 어플리케이션 관리자가 아닌 악성 유저가 일부로 심어놓은 악성 코드를 실행하게 함으로써 발생하는 취약점이다. 악의적인 사용자는 공격하려는 사이트에 다른 유저로 하여금 악의적인 스크립트를 실행하도록 유도하여 정보를 편취하거나 의도치 않는 행동을 유발하게 한다.
XSS에는 다음과 같은 유형이 있다. (참고 블로그)
Reflected XSS
Stored XSS
DOM based XSS
DOM Purify는 2014년 2월에 처음 만들어졌으며, 모던 브라우저에서 호환되되는 노드 패키지이다. 이 패키지를 이용하면 악의적인 코드를 안전한 코드로 만들 수 있다.
1) 클라이언트 사이드
<script type="text/javascript" src="src/purify.js"></script>
const $element = document.getElementByClassName('conatiner');
let dirty = '<img src="x">'
$element.innerHTML = DOMPurify.sanitize(dirty);
2) Node.js 환경
$npm install dompurify
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const clean = DOMPurify.sanitize(dirty);
Document.prototype.createElement(tagName)는 태그 이름을 통해서 새로운 노드를 생성한다.
const $li = document.createElement('li');
생성한 노드는 DOM에 추가되지 않고 홀로 존재하는 상태이다.
이후 Node.prototype.appendChild(childNode) 메서드를 통해 해당 노드를 DOM 트리 상에서 특정 요소노드의 자식 노드로 붙혀준다.
$elelement.appendChild($li);
[참고 자료]
XSS 종류
DOM Based XSS
자바스크립트 Deep Dive - 이웅모