[TECH-QA] ๋ฆฌ์•กํŠธ์—์„œ XSS ๋ฐฉ์–ด

React๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ XSS(ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฝํŒ…) ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. React์˜ ์„ค๊ณ„ ์ž์ฒด๊ฐ€ XSS ์ทจ์•ฝ์ ์„ ์ค„์ด๋„๋ก ๋งŒ๋“ค์–ด์ ธ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด๋‚˜ ๋™์  ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํŠน์ • ์ƒํ™ฉ์—์„œ๋Š” ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ React์˜ XSS ๋ฐฉ์–ด ๋ฉ”์ปค๋‹ˆ์ฆ˜๊ณผ ์ฃผ์˜์‚ฌํ•ญ์„ ์ •๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

React์˜ ๊ธฐ๋ณธ XSS ๋ฐฉ์–ด ๋ฉ”์ปค๋‹ˆ์ฆ˜

React๋Š” JSX์™€ ๋‚ด๋ถ€ ๋ Œ๋”๋ง ๋ฐฉ์‹ ๋•๋ถ„์— XSS ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ž๋™ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ

React๋Š” JSX์—์„œ ๋™์  ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ, ๊ธฐ๋ณธ์ ์œผ๋กœ HTML ํŠน์ˆ˜ ๋ฌธ์ž๋ฅผ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, <, >, & ๊ฐ™์€ ๋ฌธ์ž๋Š” &lt; &gt; &amp; ๋กœ ๋ณ€ํ™˜๋˜์–ด ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
function App() {
  const userInput = "<script>alert('XSS')</script>";
  return <div>{userInput}</div>;
}
๋ธŒ๋ผ์šฐ์ €์— ๊ฐ€ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ ํ›„ &lt;script&gt;alert('XSS')&lt;/script&gt; ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, ์Šคํฌ๋ฆฝํŠธ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Œ. React๋Š” {} ๋‚ด๋ถ€์˜ ๋ฌธ์ž์—ด์„ textContent๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ HTML๋กœ ํ•ด์„๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

JSX์˜ ์•ˆ์ „ํ•œ ๊ตฌ์กฐ

JSX๋Š” HTML์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” JavaScript ๊ฐ์ฒด๋กœ ์ปดํŒŒ์ผ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ง์ ‘ HTML๋กœ ์‚ฝ์ž…๋˜์ง€ ์•Š๊ณ , React๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” DOM API๋ฅผ ํ†ตํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.
{userInput}๋Š” React.createElement๋กœ ๋ณ€ํ™˜๋˜์–ด ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋จ.

์†์„ฑ ์ด์Šค์ผ€์ดํ”„

HTML ์†์„ฑ(์˜ˆ: value, href)์— ๋™์  ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•  ๋•Œ๋„ React๋Š” ํŠน์ˆ˜ ๋ฌธ์ž๋ฅผ ์ด์Šค์ผ€์ดํ”„ํ•˜์—ฌ ์•…์„ฑ ์ฝ”๋“œ(์˜ˆ: javascript:alert('XSS'))๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ dangerouslySetInnerHTML ์‚ฌ์šฉ.

์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜์—ฌ XSS ์ทจ์•ฝํ•˜๋‹ค.
const userInput = "<script>alert('XSS')</script>";
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;

๐Ÿ“ ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ• dangerouslySetInnerHTML ์‚ฌ์šฉ

์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์‚ฝ์ž…ํ•˜๊ธฐ ์ „์— ๋ฐ˜๋“œ์‹œ DOMPurify ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ •ํ™”(sanitize).
import DOMPurify from "dompurify";
const userInput = "<script>alert('XSS')</script>";
const sanitized = DOMPurify.sanitize(userInput);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
React์—์„œ XSS ๋ฐฉ์–ด๋ฅผ ๊ฐ•ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•
React์˜ ๊ธฐ๋ณธ ๋ฐฉ์–ด์— ๋”ํ•ด ์ถ”๊ฐ€์ ์ธ ๋ณด์•ˆ ์กฐ์น˜๋ฅผ ์ ์šฉํ•˜๋ฉด ๋” ์•ˆ์ „ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฒ€์ฆ

  • ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„์—์„œ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜์—ฌ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ํ˜•์‹(์˜ˆ: ์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ, ํŠน์ˆ˜ ๋ฌธ์ž)์„ ์ฐจ๋‹จ.
  • ์˜ˆ: ์ด๋ฉ”์ผ ์ž…๋ ฅ์€ ์ด๋ฉ”์ผ ํ˜•์‹์ด ๋งž๋Š”์ง€ ํ™•์ธ.

๐Ÿ“ DOMPurify ์‚ฌ์šฉ

DOMPurify๋Š” ์ž…๋ ฅ๋œ HTML ๋ฌธ์ž์—ด์„ ๋ถ„์„ํ•˜๊ณ , ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์š”์†Œ๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ ๋ณ€ํ™˜ํ•˜์—ฌ XSS ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
<script>alert('XSS')</script>
  • "" (๋นˆ ๋ฌธ์ž์—ด,