지금 진행하고 있는 프로젝트에서 텍스트 에디터로 작성한 글을 리스트에서 태그 없이 보여주기 위해 dangerouslySetInnerHTML
을 사용하고 있었다.
dangeouslySetInnerHTML? 말 그대로 하면 innerHTML을 위험하게 설정한다 라는 뜻인데... 사용하면서 이런 이름이 지어진 이유가 궁금했고, innerHTML을 사용할 때의 문제점에 대해서도 알아보고 싶어졌다.
dangerouslySetInnerHTML은 브라우저 DOM에서 innerHTML을 사용하기 위한 React의 대체 방법입니다. 일반적으로 코드에서 HTML을 설정하는 것은 사이트 간 스크립팅 공격에 쉽게 노출될 수 있기 때문에 위험합니다. 따라서 React에서 직접 HTML을 설정할 수는 있지만, 위험하다는 것을 상기시키기 위해 dangerouslySetInnerHTML을 작성하고 __html 키로 객체를 전달해야 합니다.
출처: https://ko.legacy.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
일반적으로 innerHTML을 사용하여 직접 HTML을 설정하는 것은 사이트 간 스크립팅 공격 즉, "XSS(Cross-Site Scripting) 공격"에 취약하기 때문에 위험성을 상기시켜주기 위해 dangerouslySetInnerHTML을 작성하는 것이라고 한다.
나의 경우 아래와 같이 코드를 작성했는데 나도 모르게 위험성이 있는 코드를 작성하고 있었던 것이었다...!
dangerouslySetInnerHTML={{ __html: content }}
그래서 XXS 공격을 통해 일어나는 문제점은 뭘까?
좀 더 자세히 알아보자.
크로스 사이트 스크립팅 또는 교차 사이트 스크립팅(Cross Site Scripting, XSS)은 공격자가 상대방의 브라우저에 스크립트가 실행되도록 해 사용자의 세션을 가로채거나, 웹사이트를 변조하거나, 악의적 콘텐츠를 삽입하거나, 피싱 공격을 진행하는 것을 말합니다. XSS 공격은 스크립트 언어와 취약한 코드를 공격 대상으로 합니다.
출처: https://nordvpn.com/ko/blog/xss-attack/
누구나 HTML을 변경할 수 있게 되면 악의적인 코드를 심어 문제를 야기할 수 있다는 것이다. 그렇다면 이런 문제의 해결책은 어떤 것이 있을지 조사해봤다.
innerHTML을 정제하여 사용하기 위해 대체적으로 2가지의 라이브러리를 사용하고 있는 것 같다.
허용된 태그와 속성 외에 html 태그를 허용하지 못하도록 한다.
sanitize-html은 Node.js와 함께 사용하도록 고안되었으며 Node 10 이상을 지원한다. (브라우저에서의 사용도 지원하지만 서버에서의 사용을 권한다.)
출처: https://www.npmjs.com/package/sanitize-html?activeTab=readme
DOM 레벨에서 HTML을 sanitize 해주고 XSS 공격을 막아준다
DOMPurify는 HTML을 삭제하고 XSS 공격을 방지합니다. 더티 HTML로 가득 찬 문자열을 DOMPurify에 제공할 수 있으며 달리 구성하지 않는 한 깨끗한 HTML이 포함된 문자열을 반환한다.
현재 19개의 다양한 브라우저를 다루고 있으며 앞으로 더 많은 브라우저가 포함될 예정이며 jsdom 에서 DOMPurify를 실행하는 Node.js v16.x, v17.x, v18.x 및 v19.x를 다룬다.
출처: https://github.com/cure53/DOMPurify
나는 현재 브라우저 상에서 라이브러리를 사용하고 있다는 이유와 패키지 다운로드 수가 높고 최근까지도 활발하게 업데이트 되고 있는 DOMPurity를 사용했다.
그렇지만 조사하는 김에 두 라이브러리의 사용방법을 같이 알아보았다.
yarn add dompurify @types/dompurify
import DOMPurify from "dompurify"
https://github.com/cure53/DOMPurify 페이지에서 여러 가지 속성을 찾아 사용할 수 있다.
나는 모든 태그 및 속성을 없애고 싶었기 때문에 아래와 같이 태그와 속성 중 아무것도 허용하지 않는 코드로 작성했다.
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content, { ALLOWED_TAGS: [], ALLOWED_ATTR: []}),
yarn add sanitize-html @types/sanitize-html
"types": [
"@types/sanitize-html" //추가
]
import sanitizeHtml from "sanitize-html"
allowedTags와 allowedAttributes 옵션을 false로 설정하면 모든 태그와 속성을 허용한다. 아래는 span 태그만을 허용할 때의 코드이다.
const cleaned = sanitizeHtml(content, {
allowedTags: ["span"],
})
dangerouslySetInnerHTML={{ __html: cleaned }}
이번 조사를 통해 XXS 공격의 위험성에 대해 다시 한번 상기시키는 시간이 되었고, 좀 더 안전하게 dangerouslySetInnerHTML를 사용할 수 있는 방법에 대해서 알게 되었다.
적용할 수 있는 라이브러리의 사용 방법을 알아보았으니 필요에 따라 적절하게 골라서 사용하면 좋을 것 같다.