사용자 글에 dangerouslySetInnerHTML을 썼다가 스크립트가 실행된 사고
문제 발생
사용자가 작성한 게시글을 HTML 서식(굵게, 링크 등)까지 표시하려고 dangerouslySetInnerHTML로 그대로 렌더링했는데, 한 사용자가 게시글에 <script> 태그를 심어 다른 방문자의 브라우저에서 임의 스크립트가 실행되는 문제가 발생했습니다.
function PostContent({ html }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />; // 사용자 입력을 그대로 삽입
}원인 분석
이건 **저장형 XSS(Stored Cross-Site Scripting)**의 전형적인 사례입니다. 브라우저는 innerHTML에 들어간 내용을 실제 HTML로 파싱합니다 — 그 안에 <script> 태그나 onerror="..." 같은 이벤트 핸들러 속성이 있으면, 그것도 똑같이 실행 가능한 코드로 취급합니다.
공격자가 자신의 게시글에 악성 스크립트를 심어두면, 그 글을 읽는 모든 다른 사용자의 브라우저에서 그 스크립트가 실행됩니다 — 로그인 세션 쿠키 탈취, 사용자 몰래 다른 요청 전송, 피싱 페이지로 리다이렉트 등 다양한 공격으로 이어질 수 있습니다. 공격자 본인이 아니라 그 콘텐츠를 소비하는 다른 사용자가 피해자가 된다는 점에서 특히 위험합니다.
해결 방안
- 가능하면 애초에
dangerouslySetInnerHTML을 쓰지 않습니다. React는 기본적으로 텍스트를 문자열로 렌더링할 때 자동으로 이스케이프 처리하므로, 서식이 필요 없는 일반 텍스트라면 그냥{content}로 렌더링하는 것이 가장 안전합니다. - 서식(굵게, 링크, 목록 등)이 정말 필요하다면, 사용자 입력을 Markdown 같은 안전한 서식 언어로 받고, 실제 렌더링은
dangerouslySetInnerHTML이 아니라 안전한 마크다운 렌더러(예: React 엘리먼트 트리를 직접 만드는 라이브러리)로 처리합니다 — 이 방식은 애초에 원본 사용자 입력이 HTML로 파싱되는 경로 자체가 없어 XSS 표면이 원천적으로 없습니다. - 불가피하게 사용자 입력을 HTML로 렌더링해야 하는 경우(리치 텍스트 에디터 등)라면, 반드시 DOMPurify 같은 검증된 sanitizer 라이브러리로 위험한 태그/속성(
<script>,on*이벤트 핸들러,javascript:URL 등)을 제거한 뒤에만dangerouslySetInnerHTML에 넣습니다.
import DOMPurify from "dompurify";
function PostContent({ html }) {
const clean = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}- 직접 정규식으로
<script>태그만 걸러내는 방식은 충분하지 않습니다 — 이벤트 핸들러 속성,javascript:프로토콜, 인코딩 우회 등 회피 기법이 매우 다양해서, 검증되지 않은 자체 필터링은 뚫릴 가능성이 높습니다. 반드시 검증된 sanitizer 라이브러리를 씁니다. - JSON-LD 같은 HTML이 아닌 데이터를 스크립트 태그 안에 넣는 경우도 별개로 주의가 필요합니다 — 그 경우엔 sanitize가 아니라
</script>시퀀스 자체를 이스케이프하는 다른 방어가 필요합니다(용도가 다른 문제입니다).
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.