설명
React의 dangerouslySetInnerHTML는 전달된 HTML을 그대로 DOM에 렌더링합니다. 사용자 입력이나 신뢰되지 않은 HTML을 정화 없이 __html에 전달하면 위험한 이벤트 핸들러나 악성 링크 등이 삽입되어 XSS로 이어질 수 있습니다.
잠재적 영향
- 세션 탈취, 토큰 탈취, DOM 변조가 발생할 수 있습니다.
- 악성 스크립트가 사용자 브라우저에서 실행되어 추가 API 호출이나 피싱 UI 삽입이 가능해집니다.
- CMS, 마크다운 변환, 댓글 렌더링 같은 HTML 출력 기능 전반이 공격면이 됩니다.
해결 방법
dangerouslySetInnerHTML사용을 최소화하고, 가능하면 일반 JSX 출력으로 대체하세요.- HTML 렌더링이 필요하면 DOMPurify 같은 검증된 HTML 정화 도구로 처리한 결과만 사용하세요.
- 사용자 입력을 직접
__html에 넣지 마세요.
예시
변경 전
javascript
import React from "react";
export function Bad({ html }) {
return <section dangerouslySetInnerHTML={{ __html: html }} />;
}
변경 후
javascript
import React from "react";
import DOMPurify from "dompurify";
export function Good({ html }) {
return <section dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />;
}
설명:
- 변경 전: 사용자 입력을 정화 없이
dangerouslySetInnerHTML에 전달하므로 XSS가 발생할 수 있습니다. - 변경 후: HTML을 렌더링하기 전에 DOMPurify로 정화해 스크립트나 위험한 속성을 제거합니다.
striptags 옵션 경계
javascript
import React from "react";
import stripTags from "striptags";
export function UnsafeAllowedTags({ html }) {
return <section dangerouslySetInnerHTML={{ __html: stripTags(html, "<img>") }} />;
}
export function UnsafeReplacement({ html }) {
return <section dangerouslySetInnerHTML={{ __html: stripTags(html, [], "<img src=x onerror=alert(1)>") }} />;
}
export function DefaultStripping({ html }) {
return <section dangerouslySetInnerHTML={{ __html: stripTags(html) }} />;
}
태그를 허용하면 위험한 속성까지 남을 수 있고, 대체 문자열로 HTML을 다시 삽입할 수도 있습니다. 태그 제거를 HTML 정화와 같은 것으로 취급하지 마세요. HTML을 보존할 필요가 없다면 일반 JSX의 {html} 출력이 더 단순합니다.