์๋ก์ด DOM API, setHTML์ ์์๋ณด์
์น ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ๋์ ์ผ๋ก HTML์ ์ฝ์
ํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ด๋ ๊ฐ์ฅ ํํ๊ฒ ์ฌ์ฉ๋๋ ๋ฐฉ๋ฒ์ innerHTML์ด์ง๋ง, ์ด ๋ฐฉ๋ฒ์ ํฌ๋ก์ค ์ฌ์ดํธ ์คํฌ๋ฆฝํ
(XSS) ๊ณต๊ฒฉ์ ์ทจ์ฝํ๋ค๋ ํฐ ๋จ์ ์ด ์์ต๋๋ค. ๊ทธ๋์ ๋ง์ ๊ฐ๋ฐ์๋ค์ด DOMPurify์ ๊ฐ์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด HTML์ ์์ ํ๊ฒ ์ฝ์
ํด์์ต๋๋ค.
์ด์ ๋ ์น ํ์ค์ ๋ด์ฅ๋ ์์ ํ ๋ฐฉ๋ฒ์ผ๋ก HTML์ ์ฝ์
ํ ์ ์๊ฒ ๋ ์ ๋ง์
๋๋ค. ๋ฐ๋ก ์๋ก์ด DOM API์ธ setHTML()์ด ๋ฑ์ฅํ๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด ๊ธ์์๋ setHTML()์ด ๋ฌด์์ธ์ง, ์ ํ์ํ์ง, ๊ทธ๋ฆฌ๊ณ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง์ ๋ํด ์์ธํ ์์๋ณด๊ฒ ์ต๋๋ค.
setHTML()์ด๋ ๋ฌด์์ธ๊ฐ์?
setHTML()์ Element ์ธํฐํ์ด์ค์ ์๋ก์ด ๋ฉ์๋๋ก, ์ด๋ฆ์์ ์ ์ ์๋ฏ์ด HTML ๋ฌธ์์ด์ DOM ์์์ ์์ ํ๊ฒ ์ค์ ํ๋ ์ญํ ์ ํฉ๋๋ค. ์ด ๋ฉ์๋๋ ๋จ์ํ HTML์ ์ฝ์
ํ๋ ๊ฒ์ ๋์ด, ๋ด์ฅ๋ HTML Sanitizer API๋ฅผ ์ฌ์ฉํด ๋ฌธ์์ด์ ํ์ฑํ๊ณ ์๋
(sanitize)ํ๋ ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค.
setHTML()์ ์ฃผ์ ํน์ง์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- XSS ๋ฐฉ์ง: ์
๋ ฅ๋ HTML ๋ฌธ์์ด์์ ์ ์ฌ์ ์ผ๋ก ์ํํ
<script>ํ๊ทธ๋onerror์ ๊ฐ์ ์ธ๋ผ์ธ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฑ XSS ๊ณต๊ฒฉ์ ์ ๋ฐํ ์ ์๋ ์์๋ฅผ ์๋์ผ๋ก ์ ๊ฑฐํฉ๋๋ค. - ์ ํจ์ฑ ๊ฒ์ฌ: ํ์ฌ ์์์ ์ปจํ ์คํธ์ ๋ง์ง ์๋ ์๋ชป๋ ์์๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
- ๊ธฐ๋ณธ Sanitizer ์ ๊ณต: ๋ณ๋์ ์ค์ ์์ด๋ ๊ธฐ๋ณธ Sanitizer ๊ตฌ์ฑ์ ํตํด ์์ ํ๊ฒ HTML์ ์ฝ์ ํ ์ ์์ต๋๋ค.
MDN ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด, setHTML()์ ํ์ฉ๋์ง ์์ HTML ์ํฐํฐ๋ฅผ ์ ๊ฑฐํ ๋ฟ๋ง ์๋๋ผ, Sanitizer ์ค์ ์์ ํ์ฉ๋์๋๋ผ๋ XSS์ ์์ ํ์ง ์์ ์์๋ ์์ฑ์ ๋ฌด์กฐ๊ฑด ์ ๊ฑฐํฉ๋๋ค. ๋๋ถ์ ๊ฐ๋ฐ์๋ ๋ณด์ ๊ฑฑ์ ์ ๋๊ณ ๋น์ฆ๋์ค ๋ก์ง์ ๋ ์ง์คํ ์ ์๊ฒ ๋ฉ๋๋ค.
์ setHTML()์ ์ฌ์ฉํด์ผ ํ ๊น?
innerHTML์ด ์๋๋ฐ ๊ตณ์ด setHTML()์ ์จ์ผ ํ๋ ์ด์ ๊ฐ ๊ถ๊ธํ์ค ๊ฒ๋๋ค. ๋ ๋ฉ์๋๋ฅผ ๋น๊ตํ๋ฉฐ setHTML()์ ์ฅ์ ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
innerHTML๊ณผ์ ๋น๊ต
innerHTML์ ์ฌ์ฉํ๊ธฐ ๊ฐํธํ์ง๋ง, ๊ฐ์ฅ ํฐ ๋ฌธ์ ๋ ๋ณด์์
๋๋ค. ์ธ๋ถ์์ ๋ฐ์ ์ ๋ขฐํ ์ ์๋ ๋ฌธ์์ด์ innerHTML์ ๊ทธ๋๋ก ํ ๋นํ๋ฉด, ๊ณต๊ฒฉ์๊ฐ ์ฝ์
ํ ์
์ฑ ์คํฌ๋ฆฝํธ๊ฐ ๊ทธ๋๋ก ์คํ๋ ์ ์์ต๋๋ค.
const userInput = `<img src=x onerror="alert('XSS ๊ณต๊ฒฉ!')">`;
const container = document.getElementById('container');
// userInput์ ํฌํจ๋ ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋์ด ๊ฒฝ๊ณ ์ฐฝ์ด ๋น๋๋ค.
container.innerHTML = userInput;
๋ฐ๋ฉด, setHTML()์ ์ด๋ฐ ์ํ์ ์์ฒ์ ์ผ๋ก ์ฐจ๋จํฉ๋๋ค.
const userInput = `<img src=x onerror="alert('XSS ๊ณต๊ฒฉ!')">`;
const container = document.getElementById('container');
// onerror ์์ฑ์ด Sanitizer์ ์ํด ์ ๊ฑฐ๋์ด ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋์ง ์์ต๋๋ค.
container.setHTML(userInput);
setHTMLUnsafe()์์ ๋น๊ต
setHTML()๊ณผ ํจ๊ป setHTMLUnsafe()๋ผ๋ ๋ฉ์๋๋ ์์ต๋๋ค. ์ด๋ฆ์ "Unsafe"๊ฐ ๋ถ์ ๊ฒ์์ ์ ์ ์๋ฏ์ด, ์ด ๋ฉ์๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก HTML์ ์๋
ํ์ง ์์ต๋๋ค. XSS์ ์ทจ์ฝํ ์์๋ฅผ ํฌํจ์์ผ์ผ ํ๋ ์์ฃผ ํน์ํ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด setHTML() ์ฌ์ฉ์ด ๊ฐ๋ ฅํ ๊ถ์ฅ๋ฉ๋๋ค. setHTMLUnsafe()๋ ์ ๋ขฐํ ์ ์๋ ์์ค์ HTML์ ์ฝ์
ํ ๋๋ง ์ ํ์ ์ผ๋ก ์ฌ์ฉํด์ผ ํฉ๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก, setHTML()์ innerHTML์ ํธ๋ฆฌํจ์ ์ ์งํ๋ฉด์ ๋ณด์์ฑ์ ํฌ๊ฒ ๋์ธ, innerHTML์ ์์ ํ ๋์์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
setHTML() ์ฌ์ฉ ๋ฐฉ๋ฒ
setHTML()์ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค !
// ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ
const unsanitizedString = "์ด๊ฑด <b>์์ ํ</b> HTML์
๋๋ค. <script>alert('์ด๊ฑด ์คํ๋์ง ์์์!')</script>";
const targetElement = document.getElementById("target");
// ๊ธฐ๋ณธ Sanitizer๋ฅผ ์ฌ์ฉํด HTML ์ฝ์
targetElement.setHTML(unsanitizedString);
์ ์ฝ๋๋ฅผ ์คํํ๋ฉด <script> ํ๊ทธ๋ ์ ๊ฑฐ๋๊ณ "์ด๊ฑด ์์ ํ HTML์
๋๋ค."๋ผ๋ ํ
์คํธ๋ง ๋ ๋๋ง๋ฉ๋๋ค.
options.sanitizer๋ก ์ฌ์ฉ์ ์ ์ํ๊ธฐ
setHTML()์ ๋ ๋ฒ์งธ ์ธ์๋ก options ๊ฐ์ฒด๋ฅผ ๋ฐ์ Sanitizer์ ๋์์ ์ ์ดํ ์ ์์ต๋๋ค. options.sanitizer ์์ฑ์ ์ฌ์ฉํด ํ์ฉํ๊ฑฐ๋ ์ ๊ฑฐํ ํ๊ทธ ๋ฐ ์์ฑ์ ์ง์ ์ง์ ํ ์ ์์ต๋๋ค.
const unsanitizedString = "<div>div ํ๊ทธ</div> <p>p ํ๊ทธ</p> <button>button ํ๊ทธ</button>";
const targetElement = document.getElementById("target");
// 1. Sanitizer ๊ฐ์ฒด๋ฅผ ์ง์ ์์ฑํ์ฌ ์ ๋ฌ
// div, p, button ํ๊ทธ๋ง ํ์ฉํฉ๋๋ค.
const customSanitizer = new Sanitizer({
elements: ["div", "p", "button"],
});
targetElement.setHTML(unsanitizedString, { sanitizer: customSanitizer });
// 2. SanitizerConfig ๊ฐ์ฒด๋ฅผ ์ธ๋ผ์ธ์ผ๋ก ์ ๋ฌ
// div์ p ํ๊ทธ๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
targetElement.setHTML(unsanitizedString, {
sanitizer: { removeElements: ["div", "p"] },
});
SanitizerConfig๋ฅผ ์ฌ์ฉํ ๋ allowElements์ removeElements๋ฅผ ๋์์ ์ ์ํ๋ ๋ฑ ์ ๊ทํ๋์ง ์์ ์ค์ ์ ์ ๋ฌํ๋ฉด TypeError๊ฐ ๋ฐ์ํ๋ ์ฃผ์ํด์ผ ํฉ๋๋ค.
๋ธ๋ผ์ฐ์ ํธํ์ฑ์ ์์ง๊น์งโฆ?
์์ฝ๊ฒ๋ setHTML()์ ์์ง ์คํ์ ์ธ ๊ธฐ๋ฅ์ด๋ฉฐ, ๊ธ์ ์์ฑํ๋ ์์ ๊ธฐ์ค์ผ๋ก ๋๋ถ๋ถ์ ์ฃผ์ ๋ธ๋ผ์ฐ์ ์์ ์ง์๋์ง ์์ต๋๋ค. Firefox Nightly ๋ฒ์ ์์ ๊ธฐ๋ณธ์ผ๋ก ํ์ฑํ๋์์ผ๋ฉฐ, ์ ์ฐจ ๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ๋ก ํ์ฐ๋ ๊ฒ์ผ๋ก ๊ธฐ๋๋ฉ๋๋ค.
๋ฐ๋ผ์ ์ค์ ํ๋ก๋์ ํ๊ฒฝ์์ ์ฌ์ฉํ๊ธฐ ์ ์๋ ๋ฐ๋์ MDN์ ๋ธ๋ผ์ฐ์ ํธํ์ฑ ํ๋ฅผ ํ์ธํด์ผ ํฉ๋๋ค. ํธํ์ฑ์ด ํ๋ณด๋๊ธฐ ์ ๊น์ง๋ Sanitizer API Polyfill์ ์ฌ์ฉํด ๋น์ทํ ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณผ ์ ์์ต๋๋ค.
์ปค๋ฎค๋ํฐ์ ์์
setHTML()์ ๋ฑ์ฅ์ ๋ง์ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ์์ ๊ธ์ ์ ์ธ ๋ฐ์์ ์ป๊ณ ์์ต๋๋ค. ํนํ Hacker News์ ๊ฐ์ ํฌ๋ผ์์๋ "25๋
๋ง์ ๋๋์ด ๋์๋ค"๋ฉฐ ํ์ํ๋ ๋ถ์๊ธฐ์
๋๋ค.
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ ๊ฐ์: ๊ฒฝ๋ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Lit์ ๊ฐ๋ฐ์๋
setHTML()์ด ํ์ค์ผ๋ก ์๋ฆฌ ์ก์ผ๋ฉดunsafeHTML์ง์์ด ๋์ ์ด๋ฅผ ํ์ฉํ์ฌ DOMPurify์ ๊ฐ์ ์ธ๋ถ ์์กด์ฑ ์์ด๋ ์์ ํ HTML ๋ ๋๋ง์ ๊ตฌํํ ์ ์์ ๊ฒ์ด๋ผ๋ฉฐ ๊ธฐ๋๊ฐ์ ํํ์ต๋๋ค. DOMPurify๋ ๊ฐ๋ ฅํ์ง๋ง,lit-html์ฝ์ด๋ณด๋ค ๋ช ๋ฐฐ ๋ ํฌ๊ธฐ ๋๋ฌธ์ ๋ด์ฅ API์ ๋ฑ์ฅ์ ํฐ ์ด์ ์ ๋๋ค. innerHTML์ ์์ ํ ๋์: ๋ง์ ๊ฐ๋ฐ์๋ค์ดsetHTML()์innerHTML์ ์์ ํ ๋ฒ์ ์ด์, npm์์ ๋๋ฆฌ ์ฐ์ด๋ DOMPurify์ ๋ด์ฅ ๋ฒ์ ์ผ๋ก ์ดํดํ๊ณ ์์ต๋๋ค. ์ด๋ ์น ํ๋ซํผ ์์ฒด์ ๋ณด์์ฑ์ด ํ ๋จ๊ณ ๋ฐ์ ํ์์ ์๋ฏธํฉ๋๋ค.
๊ฐ์ธ์ ์ผ๋ก ์ ๋ง ๋ฐ๊ฐ์ด ์์์ ๋๋ค !
setHTML()์ innerHTML์ ๊ณ ์ง์ ์ธ ๋ณด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์น ํ์ค์ ๋ด์ฅ๋ ์์ ํ HTML ์ฝ์
๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ ์ค์ํ API์
๋๋ค. ๋น๋ก ์์ง ์คํ ๋จ๊ณ์ ์์ง๋ง, ์ด ๊ธฐ๋ฅ์ด ๋ชจ๋ ๋ธ๋ผ์ฐ์ ์ ์ ์์ผ๋ก ๋์
๋๋ฉด ๊ฐ๋ฐ์๋ค์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ์ ์ค์ด๊ณ ๋์ฑ ์์ ํ๊ณ ๊ฒฌ๊ณ ํ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌ์ถํ ์ ์๊ฒ ๋ ๊ฒ์
๋๋ค.
์ง๊ธ ๋น์ฅ ํ๋ก๋์
์ ์ ์ฉํ๊ธฐ๋ ์ด๋ ต๊ฒ ์ง๋ง, ์ด ์๋ก์ด API์ ์กด์ฌ๋ฅผ ์ธ์งํ๊ณ ํ
์คํธํด๋ณด๋ฉด์ ๋ฏธ๋๋ฅผ ์ค๋นํ๋ ๊ฒ์ ์ด๋จ๊น์? setHTML()์ด ๊ฐ์ ธ์ฌ ๋ ์์ ํ ์น ๊ฐ๋ฐ ํ๊ฒฝ์ ๊ธฐ๋ํด ๋ด
๋๋ค.
๊ฐ์ธ์ ์ผ๋ก ์ง๊ธ ์ด ๋ธ๋ก๊ทธ๋ HTML์ ์ง์ ์ ์ผ๋ก ๋ค๋ฃจ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ฐ ์ ๋ง ํฌ์์์ด๋ค์! ๐คฃ
์ผ๋ฅธ ์์ ํ ๋์์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค ! ๐ฅน