안녕하세요

XSS 방어 DOMpurify - JavaScript HTML 삽입 방지 방법 본문

Next13 블로그 프로젝트

XSS 방어 DOMpurify - JavaScript HTML 삽입 방지 방법

sakuraop 2023. 9. 20. 17:19

목차

1. DOMpurify란

  • XSS 공격과 DOMpurify.sanitize()

2. DOMpurify 설치 Next.js - TypeScript 

 

3. 에러 발생: Next.js DOMPurify.sanitize() shows TypeError:   dompurify__WEBPACK_IMPORTED_MODULE_5___default.a.sanitize is not a function

  • 해결 => isomorphic-dompurify 설치

4. 에러 발생: Error: Hydration failed because the initial UI does not match what was rendered on the server.

  • 원인 1: html 구조가 이상하면 나타나는 문제 
  • 원인 2: server의 pre-render DOM tree와 실제 렌더링된 React DOM tree가 달라서 발생하는 경고

1. DOMpurify란

DB에 저장된 게시물의 contents는 HTML tag를 저장한 형태이기 때문에

해커가 script를 포함하여 게시물을 작성할 경우 애플리케이션의 정보가 탈취당할 수 있다. 

 

quill을 이용하여 저장한 contents

이러한 공격을 XSS attack이라고 하는데, HTML 필터링을 거쳐 악의적인 스크립트를 제거하여 안전한 HTML로 변환한다.

위의 필터링 과정을 sanitize라고 하며 DOMpurify의 주요 기능이다.


2. DOMpurift 설치(Next.js, TypeScript에서 에러남) => 3.으로

npm i dompurify
npm i dompurify-type

 

dompurify를 설치하고 이용 방법은 간단하다

            {<div dangerouslySetInnerHTML={{ __html: sanitize(postData.contents) }} />}
  • tag에 dangerouslySetInnerHTML 속성을 추가하고, __html: sanitize(tag) 를 인자로 전달하면 된다.

 

그러나 Next.js에서는 에러가 발생한다.

(Next.js 에서 react component에 dompurify를 적용할 때 발생하는 문제)

 

module-error 
Next.js DOMPurify.sanitize() shows TypeError: dompurify__WEBPACK_IMPORTED_MODULE_5___default.a.sanitize is not a function

 

원인: window 객체를 생성하지 않은 시점(client component)에서는 DOMpurify를 사용할 수 없기 때문이다.

(쉽게 말해 server나 browser에서 가능하다는 뜻)

 

해결법  https://github.com/kkomelin/isomorphic-dompurify/issues/54

이것저것 세팅을 해서 window 객체를 DOMpurify에 전달하는 방법도 찾아보면 있는데, 

 

그걸 다 준비해준 라이브러리가 있다.


isomorphic-dompurify 설치

https://www.npmjs.com/package/isomorphic-dompurify

npm i isomorphic-dompurify

 

Next.js 에서 react component에 dompurify를 적용할 때 발생하는 문제 원인인 window 객체가 존재하도록 해준다.

(JSDOM을 그려줌)

 

보통 이걸 설치하면 잘 되겠으나, 일부 상황에서 또다른 에러가 발생할 수 있다.


Hydration failed 에러

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.

 

hydration이란 말 뜻 그대로는 "수분공급"이란 뜻.

React에서 쓰이는 용어로써는 SSR과 CSR을 결합하는 방식입니다.

 

1) SSR 서버 측에서 초기 페이지 렌더링(HTML 및 JavaScript 코드를 생성)

=> 2) 초기 HTML 및 JavaScript 코드가 브라우저로 전송

=> 3) CSR JavaScript를 실행하여 페이지를 클라이언트 측에서 다시 렌더링

=> 4) React는 초기 렌더링 결과와 동일한 컴포넌트 트리 생성시도

 


원리야 그렇다 치고, 실제로 발생하는 원인을 꼽으면 다음 두 가지가 있습니다.

 

 

문제 원인 1. html 구조가 이상하면 나타나는 문제 (4) 번 상황에서 발생하는 "경고"입니다.

예를 들어서 <p> 태그 안에 <div> 태그가 있다거나,
<ul> 태그안에 <li> 태그 없이 <span>이 있다거나 할 때 발생하는 문제
=> 경고문구 
app-index.js:31 Warning: Prop `dangerouslySetInnerHTML` did not match. Server: "" Client: "<p>testEdit</p>"
    at p
    at div
    at div
    at article
    at div
    at InnerLayoutRouter 

 

해결하는 방법은 간단합니다.

올바른 태그 사용을 해주면 됩니다.

<p>안에 <div> 넣지 말고, 웹 접근성 준수하면 됩니다,

 

문제 원인 2. server의 pre-render DOM tree와 실제 렌더링된 React DOM tree가 달라서 발생하는 경고

예를 들어서 server의 pre-render된 트리에는 window, browser 같은 객체가 없는데,
React DOM에는 window나 brower를 필요로 하는 상황에서 발생

 

해결하는 방법은 상황에 따라 다를 것 같습니다. 안 겪어봐서 예시를 못 들겠습니다만

위에서 isomorphic-dompurify이 문제해결한 방식인 JSDOM을 그리는 방식으로 단서를 잡고 찾아보면 될 것 같습니다.

 

이외의 추가적인 에러 상황은 공식 문서 참조)

https://nextjs.org/docs/messages/react-hydration-error

 

Text content does not match server-rendered HTML

Using App Router Features available in /app

nextjs.org