본문 바로가기

코딩/트러블슈팅

React-Quill 게시물 저장 방식 개선하기: HTML에서 Delta로

크로스핏 박스 전용 웹사이트 'wodLog'를 개발하면서 공지사항과 오늘의 운동(WOD) 페이지에 게시판 기능이 필요했습니다.

게시물 작성을 위한 에디터 라이브러리로 React-Quill을 선택했는데, 그 이유는 다음과 같습니다.

  • npm에서 인기 있는 에디터 라이브러리 중 하나
  • 간편한 설정
  • 비교적 가벼운 번들 크기
  • 풍부한 커스터마이징 옵션

 

DB에 HTML로 저장?

React-Quill로 게시판 CRUD 기능 구현 중 게시글이 HTML 태그와 함께 DB에 저장되는 것을 발견했습니다.
예를 들어 아래 내용을 저장한다면,

드랍인은 크로스핏 경험이 있으신 분들을 위한 1회 수업/박스 이용권입니다.
무료체험은 크로스핏이 1달 미만이신 분들만 신청 부탁드립니다!

드랍인 비용은 타 박스 30,000원 / wodLog 타 지점 15,000원입니다.
감사합니다 :)

 
실제 DB에는 이렇게 HTML 형식으로 저장 및 조회되었습니다. 

<p>드랍인은 크로스핏 경험이 있으신 분들을 위한 1회 수업/박스 이용권입니다.</p>
<p>무료체험은 크로스핏이 1달 미만이신 분들만 신청 부탁드립니다!</p>
<p><br></p>
<p>드랍인 비용은 타 박스 30,000원 / wodLog 타 지점 15,000원입니다.</p>
<p>감사합니다 :)</p>

 
 작성된 내용이 HTML 태그가 포함된 문자열로 입력되기 때문에 저장 시에도 HTML 태그가 포함되어 DB에 저장되고, 그 저장된 내용이 화면에서 그대로 조회되는 것입니다.

 

XSS(Cross-Site Scripting) 보안 취약점

HTML 형식 저장은 XSS(Cross-Site Scripting) 공격에 취약하다는 문제점을 가지고 있습니다. XSS는 해커가 웹 사이트에 악성 코드를 삽입하여 의도치 않은 행동을 수행시키거나 사용자의 민감한 정보를 탈취하는 보안 취약점을 말합니다. 아래와 같이 악성 코드가 삽입될 수 있습니다. 

// 악성 코드 삽입 예시
<p>정상적인 내용</p>
<script>
  alert('해킹됨');
  document.cookie를 외부로 유출하는 코드;
</script>

 

그렇기 때문에 HTML 형식의 저장 방식은 주의해야 합니다.

 


 

여기까지 React-Quill로 게시판 CRUD 기능을 구현하며 발견한 문제점은 2가지입니다.

  1. DB 저장 시 HTML 형식으로 저장
  2. 상세 페이지에서 HTML 태그가 포함된 내용이 조회

이 2가지의 문제점 중 2번에 대한 해결책은 구글링으로 쉽게 찾을 수 있었습니다.
바로 `dangerouslySetInnerHTML`와 Dompurify 라이브러리를 사용하는 것입니다.

각각 하나씩 살펴보게 된다면,
 

dangerouslySetInnerHTML

React는 보안을 위해 기본적으로 HTML 문자열을 문자 그대로 렌더링 합니다. HTML을 실제로 렌더링 하려면 `dangerouslySetInnerHTML` 속성을 사용해야 하는데, 이름에서도 알 수 있듯이 위험한 방식입니다.

 

아래는 React 공식문서의 dangerouslySetInnerHTML 입니다.

dangerouslySetInnerHTML

This is dangerous. As with the underlying DOM innerHTML property, you must exercise extreme caution! Unless the markup is coming from a completely trusted source, it is trivial to introduce an XSS vulnerability this way.
<div dangerouslySetInnerHTML={{ __html :  HTML 태그 추가  }} />

 
 

Dompurify 라이브러리

HTML을 안전하게 살균(sanitize)하여 악성 스크립트를 제거하는 라이브러리입니다.

 

사실 Dompurify 라이브러리 없이 `dangerouslySetInnerHTML`만 사용해도 이 문제를 해결할 수 있지만, Dompurify까지 설치하는 이유는 해커의 공격을 막기 위해서입니다. 앞서 살펴봤던 XSS 같은 악성 스크립트가 있으면 살균해 주기에 이 라이브러리가 필요합니다. 그래서 `dangerouslySetInnerHTML`와 Dompurify 라이브러리까지 같이 적용해 주는 것이 좋습니다.
 
위 2가지 방법을 적용하면 게시물 상세 페이지에서 HTML 태그를 노출하지 않으면서 HTML 기능이 적용된 순수 text만 화면에 보이게 할 수 있습니다. 
 

출력된 내용

 

 

하지만 DB에 저장되는 것은...?

상세 페이지 조회는 원하는 순수 text로 잘 나왔지만, DB에 저장된 형식은 HTML 형식 그대로입니다.
앞서 살펴봤던 것처럼 자유도가 높은 HTML은 잠재적 XSS 공격 가능성, 악성 스크립트 삽입 가능성을 내포하고 있어 보안의 위험이 있으니 상세 페이지에서 조회할 때는 Dompurify로 살균해서 조회한다 쳐도 애초에 저장되는 건 HTML 형식으로 DB에 저장되는 것인데 오히려 저장부터 다른 형태로 저장해야 더 안전한 것 아닌가?라는 생각이 들게 되었습니다. 그래서 저장하는 다른 방법에 대해 찾기 시작했습니다.

 

첫 번째로 찾아봤던 것은 React-Quill이 markdown을 지원하는지 찾아보았으나 지원하지 않은다는 것을 알게 되었습니다.

(🥲 역시 쉬운 길은 없지..)

 

그래서 Quill 공식 문서를 다시 한번 읽게 되었고 드디어 Delta를 만나게 되었습니다. 
 

Delta 등장✨

Delta

  • HTML의 모호성과 복잡성 없이 모든 텍스트와 서식 정보를 설명할 수 있는 JSON 기반의 형식
  • 구조화된 데이터로 저장되어 보안성 향상
  • Quill 공식문서의 delta

Delta를 발견하고 프로젝트에 적용하여 게시물 저장 방식을 HTML을 Delta로 개선할 수 있었습니다.
아래는 최종 해결된 실제 DB에 저장된 Delta형식입니다.

{
 "ops": [
   {
     "attributes": {
       "background": "inherit"
     },
     "insert": "드랍인은 크로스핏 경험이 있으신 분들을 위한 1회 수업/박스 이용권입니다."
   },
   {
     "insert": "\n"
   },
   {
     "attributes": {
       "background": "inherit"
     },
     "insert": "무료체험은 크로스핏이 1달 미만이신 분들만 신청 부탁드립니다!"
   },
   {
     "insert": "\n\n"
   },
   {
     "attributes": {
       "background": "inherit"
     }, 
     "insert": "드랍인 비용은 타 박스 30,000원 / wodLog 타 지점 15,000원입니다."
   },
   {
     "insert": "\n"
   },
   {
     "attributes": {
       "background": "inherit"
     },
     "insert": "감사합니다 :)"
   },
   {
     "insert": "\n"
   }
 ]
}

 

물론 Delta를 사용하고 화면 출력까지의 단계는 추가되었지만, 그래도 위험도가 높은 HTML을 DB에 저장하지 않는다는 장점이 있습니다.

Delta 형식으로 저장하면 사용자 입력이 구조화된 데이터로 저장되고, 필요할 때 HTML로 변환되는 과정을 거치기 때문에 직접적인 HTML 삽입으로 인한 보안 위험을 줄일 수 있습니다.
 


기존 방식

  1. HTML 태그 그대로 DB에 저장
  2. 상세 페이지에서 `dangerouslySetInnerHTML`와 Dompurify 라이브러리를 함께 사용
  3. 화면 출력

보안이 개선된 방식

  1. HTML 태그를 Delta로 변환하여 DB에 저장
  2. 상세 페이지에서 Delta로 변환된 데이터를 HTML로 변환하고 `dangerouslySetInnerHTML`와 Dompurify 라이브러리를 함께 사용하여 조회
  3. 화면 출력

 

출력된 내용

 
 
 
마지막으로 개선 결과를 비교해 보자면 아래와 같습니다.

구분 HTML 형식 Delta 형식
저장 형식 HTML 태그가 포함된 문자열 JSON 기반의 구조화된 데이터
보안성 XSS 공격에 취약 구조화된 데이터로 안전
데이터 정제 복잡한 HTML 파싱 필요 구조화된 데이터로 처리 용이
변환 과정 직접 출력 가능 HTML 변환 과정 필요

 

 
이렇게 React-Quill 게시물 저장 방식을 HTML에서 Delta로 개선하여 보안을 강화하였습니다✨