-
dangerouslySetInnerHTML 의 보안에 대하여Studying/JavaScript 2023. 7. 13. 15:31
개요
Portfolly 프로젝트에서 '포트폴리오 등록/상세보기' 페이지를 각각 만들던 중, ReactQuill 에디터에서 작성한 글의 경우 HTML 코드 형식의 텍스트가 저장되어서 해당 내용을 상세보기 페이지에서 불러올 때는 dangerouslySetInnerHTML 를 사용해 변환해야 했습니다.
(innerHTML, dangerouslySetInnerHTML은 string을 파싱해서 HTML로 만들어줍니다. 마치 JS의 innerHTML과 같습니다.)
다들 이 방법을 사용하길래 아무 생각 없이 따라 썼는데, 멘토링 시간에 dangerouslySetInnerHTML 보안 문제에 대해서 생각해봤냐는 질문을 받았습니다.
그러고보니 이름부터가 dangerously 라고 말해주고 있는데 거기까지 생각도 안 한 채 무턱대고 사용해버렸습니다..
그래서 이번에는 dangerouslySetInnerHTML 의 보안에 대해, 또 어떻게 보안 문제를 해결하는지에 대해 조사했습니다.
dangerouslySetInnerHTML은 XSS 공격에 취약하다.
dangerouslySetInnerHTML 은 XSS 공격에 취약합니다.
XSS(Cross-Site Scripting)란?
권한이 없는 사용자가 악의적인 용도로 웹 사이트에 스크립트를 삽입하는 공격 기법dangerouslySetInnerHTML은 노출이 쉬운 Javascript 코드 위에서 바로 HTML을 설정하기 때문에, 공격자가 개입해 얼마든지 정상 데이터를 가로채고 악성 코드가 담긴 문서로 바꿔서 전달할 수 있습니다.
그 과정을 간단하게 설명하면 아래와 같습니다.
- 공격자가 악성 스크립트 코드를 HTML 문서에 삽입한다.
- ex) 쿠키에 담긴 개인정보를 가진 비밀 키를 빼내는 코드
- 공격자는 악성 스크립트가 담긴 HTML 문서를 일반 사용자에게 전달합니다.
- 일반 사용자가 공격 HTML 문서를 자신의 브라우저에서 열면서 악성 스크립트가 실행됩니다.
문제를 해결하기 위해서는 HTML string을 파싱하기 전에 데이터를 검증하는 단계가 필요합니다.
해결 방법 1. 매핑 함수로 HTML 태그 파싱
dangerouslySetInnerHTML을 사용하지 않고 dom 매핑 함수를 만들어 직접 HTML 태그를 파싱하는 방법입니다.
예를 들어 '개행1\n개행2\n개행3\n' 이라는 string을 HTML로 출력하고 싶을 때
let data = "개행1\n개행2\n개행3\n"; // return() 안 JSX 코드 안에 넣음 data.split('\n').map( line => { return (<span>{line}<br/></span>) })
위와 같은 방법으로 HTML을 반환할 수 있습니다.
아예 매핑 함수 파일을 별도 분리해서 모든 태그(에디터에서 다양한 HTML을 다룰 수 있다면 다소 귀찮아질 것 같지만)에 대한 매핑 코드를 작성하면 좋겠습니다.
해결 방법 2. DOMPurify로 보안 강화
제가 선택한 방법입니다. 바로 코드를 살균(sanitising) 하는 것입니다.
코드를 우선 파싱한 다음, 위험 요소를 모두 제거한 뒤 렌더링하는 방법입니다. 이렇게 코드를 sanitizing 하는 방법으로 두 가지가 있는데,
- DomPurify - 외부 플러그인
- HTML Sanitizer API - 브라우저 자체 제공
HTML Sanitizer API는 아직 모든 브라우저에 적용되지 않는다고 해서 DOMPurify를 선택했습니다.
그럼 DOMPurify를 사용하는 방법에 대해 알아보겠습니다.
먼저 Typescript 환경을 고려하여 dompurify를 설치합니다.
npm i dompurify npm i --save-dev @types/dompurify
그 다음 sanitizer 객체를 생성한 뒤, 화면에 그릴 <div> 태그 안에 dangerouslySetInnerHTML 속성을 추가하고 sanitizing 할 HTML 코드를 넣어줍니다.
import dompurify from "dompurify"; function useDOMPurify(){ const sanitizer = dompurify.sanitize; return ( <div dangerouslySetInnerHTML={{ __html: sanitize(setElementInlineStyle(portfolio.content)), }} ></div> ) }
그런데 DOMPurify를 사용하기 전 주의사항이 있습니다.
DOMPurify에서 기본으로 차단하는 몇몇 HTML 태그가 존재하는 것입니다.
저의 경우 <iframe> 태그가 화면에 나타나지 않아 원인을 살펴보니, DOMPurify의 blocklist 태그 중 하나였습니다.
이에 대한 해결책은 아래 게시물에서 다뤘습니다.
참고 사이트
'Studying > JavaScript' 카테고리의 다른 글
DOMpurify를 사용하면 iframe 태그를 불러오지 못 하는 이유 (0) 2023.07.22 [TypeScript] enum+union으로 문자열 타입 상수화하기 (0) 2023.07.14 공공 코로나 데이터 OpenAPI 연결 (0) 2022.08.30 [Socket.io] 웹소켓으로 그림판 채팅 만들기 (0) 2022.08.10 [Ajax] 중고차 홈페이지 만들기 (0) 2022.07.30 - 공격자가 악성 스크립트 코드를 HTML 문서에 삽입한다.