v-html 은 위험하다!!

XSS(크로스 사이트 스크립트) 를 유발할 수 있기 때문이다.
크로스 사이트 스크립팅 또는 교차 사이트 스크립팅(Cross Site Scripting, XSS)은 공격자가 상대방의 브라우저에 스크립트가 실행되도록 해 사용자의 세션을 가로채거나, 웹사이트를 변조하거나, 악의적 콘텐츠를 삽입하거나, 피싱 공격을 진행하는 것을 말합니다.
크로스 사이트 스크립트의 종류
그렇다면 어떻게 해결해야 하나?
전문가가 아니면 보안코드 작성은 위험하다.
사용자들이 많이 다운로드 받았고, 그나마 안전하다 판단이 되는 라이브러리를 사용하자!
1.
sanitize-html
패키지 설치
yarn add sanitize-html 혹은 npm install sanitize-html
JavaScript
복사
모듈 가져오기
import sanitizeHtml from 'sanitize-html';
JavaScript
복사
JS에서의 사용
const dirty = 'some really tacky HTML'; const clean = sanitizeHtml(dirty);
JavaScript
복사
옵션을 따로 지정해 주지 않는다면 default로 허용된 태그 및 속성의 목록만 허용된다! 기본 옵션은 아래와 같다.
//기본옵션 allowedTags: [ "address", "article", "aside", "footer", "header", "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "main", "nav", "section", "blockquote", "dd", "div", "dl", "dt", "figcaption", "figure", "hr", "li", "main", "ol", "p", "pre", "ul", "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn", "em", "i", "kbd", "mark", "q", "rb", "rp", "rt", "rtc", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "caption", "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr" ], disallowedTagsMode: 'discard', allowedAttributes: { a: [ 'href', 'name', 'target' ], // We don't currently allow img itself by default, but // these attributes would make sense if we did. img: [ 'src', 'srcset', 'alt', 'title', 'width', 'height', 'loading' ] }, // Lots of these won't come up by default because we don't allow them selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ], // URL schemes we permit allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ], allowedSchemesByTag: {}, allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ], allowProtocolRelative: true, enforceHtmlBoundary: false
JavaScript
복사
기본 옵션을 따르고, 태그나 속성 하나를 추가할 수 있나요?!
네. 기본으로 제공되는 옵션은 보안상 좋지만, style속성이나 자주 이용하는 태그나 속성이 빠져있어서, 사용자 커스텀이 필요한 경우가 있습니다. 아래 코드에서는 allowedAttributes 를 지정하지 않았으므로 속성값은 기본 코드를 따릅니다.
//img를 추가한 코드 const clean = sanitizeHtml(dirty, { allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img' ]) });
JavaScript
복사
모든 태그를 허용하려면요 ?!
네. 아래와 같이 값을 false로 적용하면 됩니다. 하지만 보안을 위해서라면 사용하지 않는 것이 좋습니다.
allowedTags: false, allowedAttributes: false
JavaScript
복사
모든 태그를 허용하지 않으려면요!?
네. 아래와 같이 바꿔주면 됩니다..
allowedTags: [], allowedAttributes: {}
JavaScript
복사
더 자세한 것은 이곳을 참고하세요
이번 플젝에선 2번을 사용하였다!!
2.
vue-dompurify-html 와 dompurify
https://www.npmjs.com/package/vue-dompurify-html
vue-dompurify-html은 dompurify를 적용하여 v-html에 필터를 적용하는 디렉티브입니다.
일단 두개의 라이브러리 모두 꾸준히 업데이트 되고 있고, 2022.06.01 기준 비교적 최근 업데이트 되었고, 다운로드 수도 많고 믿을만하다고 판단합니다.
설치
yarn add vue-dompurify-html (yarn add dompurify) 혹은 npm i vue-dompurify-html
JavaScript
복사
모듈 가져오기
//dompurify를 사용할 때!! import Dompurify from "dompurify";
JavaScript
복사
main.js
// main.js import { createApp } from 'vue'; import App from './App.vue'; import VueDOMPurifyHTML from 'vue-dompurify-html'; const app = createApp(App); app.use(VueDOMPurifyHTML); app.mount('#app');
JavaScript
복사
하나씩 바꿀 생각하지 말고, VS Code의 replace all 기능을 사용해서 프로젝트 전체에서 일괄 변경한다. >> 현재 v-html 코드 사용하는 곳은 preview.vue밖에 없으므로 하나만 수정!!
https://html5sec.org/ 가 다 통과되는지 테스트 해본다 html5 security cheatsheet 에 있는 테스트를 통과하는지 확인해보자.
<template> <div v-dompurify-html="rawHtml"></div> </template> <script setup> import { ref } from 'vue'; const rawHtml = ref('<span style="color: red">This should be red.</span>'); </script> //main.js import { createApp } from 'vue'; import App from './App.vue'; import VueDOMPurifyHTML from 'vue-dompurify-html'; const app = createApp(App); app.use(VueDOMPurifyHTML); app.mount('#app');
JavaScript
복사
Can I configure DOMPurify?
SURE.
// strip {{ ... }}, ${ ... } and <% ... %> to make output safe for template systems // be careful please, this mode is not recommended for production usage. // allowing template parsing in user-controlled HTML is not advised at all. // only use this mode if there is really no alternative. const clean = DOMPurify.sanitize(dirty, {SAFE_FOR_TEMPLATES: true});
JavaScript
복사
Control our allow-lists and block-lists
// allow only <b> elements, very strict const clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b']}); // allow only <b> and <q> with style attributes const clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style']}); // allow all safe HTML elements but neither SVG nor MathML // note that the USE_PROFILES setting will override the ALLOWED_TAGS setting // so don't use them together const clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {html: true}}); // allow all safe SVG elements and SVG Filters, no HTML or MathML const clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {svg: true, svgFilters: true}}); // allow all safe MathML elements and SVG, but no SVG Filters const clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {mathMl: true, svg: true}}); // change the default namespace from HTML to something different const clean = DOMPurify.sanitize(dirty, {NAMESPACE: 'http://www.w3.org/2000/svg'}); // leave all safe HTML as it is and add <style> elements to block-list const clean = DOMPurify.sanitize(dirty, {FORBID_TAGS: ['style']}); // leave all safe HTML as it is and add style attributes to block-list const clean = DOMPurify.sanitize(dirty, {FORBID_ATTR: ['style']}); // extend the existing array of allowed tags and add <my-tag> to allow-list const clean = DOMPurify.sanitize(dirty, {ADD_TAGS: ['my-tag']}); // extend the existing array of allowed attributes and add my-attr to allow-list const clean = DOMPurify.sanitize(dirty, {ADD_ATTR: ['my-attr']}); // prohibit ARIA attributes, leave other safe HTML as is (default is true) const clean = DOMPurify.sanitize(dirty, {ALLOW_ARIA_ATTR: false}); // prohibit HTML5 data attributes, leave other safe HTML as is (default is true) const clean = DOMPurify.sanitize(dirty, {ALLOW_DATA_ATTR: false});
JavaScript
복사
Control behavior relating to Custom Elements
// DOMPurify allows to define rules for Custom Elements. When using the CUSTOM_ELEMENT_HANDLING // literal, it is possible to define exactly what elements you wish to allow (by default, none are allowed). // // The same goes for their attributes. By default, the built-in or configured allow.list is used. // // You can use a RegExp literal to specify what is allowed or a predicate, examples for both can be seen below. // The default values are very restrictive to prevent accidental XSS bypasses. Handle with great care! const clean = DOMPurify.sanitize( '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: null, // no custom elements are allowed attributeNameCheck: null, // default / standard attribute allow-list is used allowCustomizedBuiltInElements: false, // no customized built-ins allowed }, } ); // <div is=""></div> const clean = DOMPurify.sanitize( '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: /^foo-/, // allow all tags starting with "foo-" attributeNameCheck: /baz/, // allow all attributes containing "baz" allowCustomizedBuiltInElements: true, // customized built-ins are allowed }, } ); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div> const clean = DOMPurify.sanitize( '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>', { CUSTOM_ELEMENT_HANDLING: { tagNameCheck: (tagName) => tagName.match(/^foo-/), // allow all tags starting with "foo-" attributeNameCheck: (attr) => attr.match(/baz/), // allow all containing "baz" allowCustomizedBuiltInElements: true, // allow customized built-ins }, } ); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>
JavaScript
복사
더 자세한 것은 이곳을 클릭
XSS 실습 사이트