개발을 하다보면, 코드 상에서 직접적으로 DOM을 조작하기도 합니다. 대표적으로 innerHtml을 사용하는 것이 직접적으로 DOM을 조작하는 것입니다.
const article = document.getElementById('soccer-article');
article.innerHTML = "<article style='color:red'>content . . .</article>";
하지만, 이렇게 '직접적으로 DOM을 조작하는 것'은 보안적인 측면에서 위험할 수 있습니다.
React에서도 이런 행동이 위험할 수 있다는 것을 상기시키기 위해, 직접 DOM을 조작할 때는 아래와 같이 어느정도 이름에서 위험도가 유추되는 'dangerouslySetInnerHtml'이라는 기능을 사용하도록 합니다.
const contentHtml = 'First · Second';
<div dangerouslySetInnerHTML={{__html: contentHtml}} />;
저는 최근에 프로젝트를 진행하면서 Markdown 형식으로 작성된 게시물을 가져와 HTML로 변환한 뒤, 변환된 HTML을 화면에 보여주는 기능을 구현한 경험이 있습니다. 이 과정에서 dangerouslySetInnerHtml를 처음 알게 되었는데요.
이번 글에서는 이렇게 프로젝트 진행 과정에서 새롭게 알게 된 dangerouslySetInnerHtml이라는 기능에 대해 조금 더 이야기해보려 합니다.
"dangerously"SetInnerHtml과 XSS
앞서 말씀드렸다시피, 이 기능은 보안적인 측면에서 위험할 수 있습니다.
그 이유는 바로 XSS(사이트 간 스크립팅) 공격에 취약하기 때문인데요. 여기서 XSS는 가장 기초적인 취약점 공격 방법 중 하나로, 악의적인 사용자가 공격하려는 사이트에 스크립트를 주입하는 기법을 말합니다.
예를 들어, 공격자가 아래와 같은 script를 사이트에 주입한다고 가정했을 때, 공격을 받는 입장에서는 화면에 무한으로 alert 창이 나타나게 되면서 어떠한 상호작용도 할 수 없는, 그런 치명적인 상황도 발생하게 되며, 쿠키 탈취, 악성 리다이렉션 등 더 심각한 악성 script를 삽입할 수 도 있습니다.
게시물을 작성하여 공유하는 어떤 커뮤니티 서비스가 있다면, 게시글이나 댓글을 작성하는 기능을 통해 이렇게 script를 주입해서 XSS 공격을 시도할 수도 있을 것 같습니다.
<script>
while(true) {
alert('This is XSS');
}
</script>
사용 전 고려해야 할 사항
지금까지의 내용들을 살펴보면 dangerouslySetInnerHtml은 최대한 지양해야 하는 기능입니다. 그렇다면, 어쩔 수 없이 사용해야 하는 상황에서는 어떻게 해야할까요?
그럴 때는 먼저, 지금이 dangerouslySetInnerHtml를 사용해도 되는 시나리오인지를 검토해야 합니다. 다음은 dangerouslySetInnerHtml를 사용할 수 있는 시나리오입니다.
1. 받은 HTML 데이터의 출처를 신뢰할 수 있을 때
받게 된 HTML이 어떠한 관리 시스템이나, 서버에서 직접 생성된 HTML이라면 dangerouslySetInnerHtml를 사용할 수 있습니다. 하지만 신뢰 가능한 HTML인지 꼼꼼히 검토해야 합니다.
2. '소독' 된 안전한 HTML일 때
여기서 '소독된 HTML'이라는 것은 HTML이 안전하지 않은 태그나 XSS 공격에 대해 검증 되었다는 의미입니다.
이를 위해서는 검증된 sanitize(소독하다) 라이브러리를 사용해야 합니다. 여기서 '소독'이라는 표현은 주로 위험에 노출된 HTML에서 악성코드 등을 추적하여 제거할 때 보편적으로 사용하는 표현입니다.
최종 점검
이렇게 적합한 시나리오들이 있고 자신이 이 시나리오와 비슷한 상황에 있다면, 당신은 dangerouslySetInnerHtml을 사용할 준비가 되었습니다.
하지만 여전히 dangerouslySetInnerHtml를 사용하는 것은 그닥 좋지 않은 방법이며, 실제로 React도 자체적으로 사용을 지양할 것을 권고하고 있습니다.
그럼에도 사용해야 한다면 . . .
그럼에도 dangerouslySetInnerHtml를 사용해야 한다면, 다행히도 여기 dangerouslySetInnerHtml을 안전하게 사용할 수 있는 몇가지 방법이 있습니다.
몇가지 방법에는 외부 라이브러리를 사용하는 방법(DOM Purify), 직접 구현하는 방법 등이 있는데요.
저는 대표적인 방법 중 하나인 'DOM Purify'라는 라이브러리를 활용하는 방법을 선택했습니다. '직접 구현하는 방법'은 아래의 링크에서 확인하실 수 있습니다.
> https://snow-line.tistory.com/204
DOM Purify
그렇다면 바로 DOM Purify를 활용한 방법을 살펴보겠습니다.
우선 DOM Purify package를 추가해주고,
$ yarn add dompurify
이렇게 아래의 코드와 같이 DOM Purify를 사용하면, 이 DOM Purify가 HTML 코드에서 잠재적인 악성코드를 추적해 안전하게 코드 자체를 sanitize 하여 안전하게 출력해줍니다.
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const sanitizeHtml = (html: string) => {
return DOMPurify(new JSDOM('<!DOCTYPE html>').window).sanitize(html);
};
export default sanitizeHtml;
하지만 코드를 보면 DOM Purify뿐만 아니라, JS DOM이라는 package도 사용하고 있는 것을 확인할 수 있는데요.
그 이유는, DOM Purify에는 DOM tree가 요구되지만 노드 환경에서는 DOM tree가 없어서 JS DOM을 사용해 window 객체를 생성하고, 생성된 객체에서 DOM Purify를 초기화 해줘야 오류 없이 DOM Purify를 사용할 수 있기 때문이라고 합니다.
참고한 자료들
https://deadsimplechat.com/blog/how-to-safely-use-dangerouslysetinnerhtml-in-react/
'Web Frontend' 카테고리의 다른 글
인상 깊었던 기술 질문 모음.zip (1) | 2024.06.24 |
---|---|
CORS anywhere를 소개합니다 (2) | 2024.04.12 |
Atomic Design Pattern 파헤치기 (0) | 2023.05.11 |