FlawOpen

flawopen.com/XSS in JavaScript/DOMPurify and innerHTML

DOMPurify만으로 innerHTML이 안전해지나요?

참고 가이드
짧은 답변

DOMPurify는 결과를 나중에 바꾸지 않고 HTML sink에 바로 넣을 때 리치 HTML을 위한 강력한 허용 목록 sanitizer입니다. 입고장에서 검사한 소포와 같습니다. 이후 다른 과정에서 다시 포장하거나 다른 경로로 넣으면 최초 검사가 최종 내용을 보장하지 않습니다. 일반 텍스트는 textContent로 넣어 HTML 파싱을 피하세요.

여전히 위험 — 오래된 결과가 sink에 도달
// 저장 시 오래된 버전/정책으로 정화함.
saveToDb(DOMPurify.sanitize(input));

// 나중에 저장 문자열을 재검증 없이 신뢰함.
el.innerHTML = fetchedFromDb;
더 안전 — HTML sink에 맞춰 정화
// 리치 HTML이 필요하면 최신 라이브러리로 정화
// HTML 컨텍스트에 넣기 직전에 수행함.
el.innerHTML = DOMPurify.sanitize(untrustedHtml, {
  USE_PROFILES: { html: true },
});

// 일반 텍스트는 HTML로 파싱하지 않음.
textNode.textContent = untrustedText;

저장 시 한 번만 정화하는 방식이 약한 이유

저장 시 정화가 자동으로 취약한 것은 아니지만 오래된 데이터 경계를 만듭니다. sanitizer 수정, 정책 변경, 정화를 건너뛴 경로 뒤에도 렌더러가 오래된 결과를 신뢰할 수 있습니다. 렌더링 시 현재 버전과 정확한 sink 정책으로 다시 정화하세요. 삽입 전에 정화된 마크업을 바꾸거나 다른 라이브러리에 넘기지 마세요. 후처리가 정화 효과를 무효화할 수 있습니다.

설정과 출력 컨텍스트가 중요합니다

DOMPurify 기본 설정은 HTML, SVG, MathML을 허용합니다. HTML만 필요하면 USE_PROFILES: { html: true }로 범위를 줄이세요. 검토 없이 태그나 속성을 추가하지 마세요. DOMPurify는 CSS sanitizer가 아니며 모든 외부 리소스 요청을 막지 않습니다. 결과를 HTML 컨텍스트에만 쓰고 JavaScript, 속성, XML, SVG, CSS에는 재사용하지 마세요.

Trusted Types와 서버 렌더링

지원되는 환경에서는 Content Security Policy로 Trusted Types를 적용해 승인된 정책을 거치지 않은 문자열을 HTML sink에서 거부하세요. DOMPurify는 TrustedHTML을 반환할 수 있지만 위험한 설정이나 후처리를 해결하지는 않습니다. 서버에서는 DOM 구현에 의존하므로 jsdom을 최신 상태로 유지하고 보안 민감한 정화에 happy-dom을 함께 쓰지 마세요.

운영 체크리스트

HTML injection sink 검색

rg -n -g '*.{js,ts,jsx,tsx}' '(innerHTML|outerHTML|insertAdjacentHTML|document\.write|dangerouslySetInnerHTML)' .
각 검색 결과에서 신뢰할 수 없는 데이터가 sink에 닿는 경로를 추적하고 삽입 직전 정화와 이후 마크업 변경 여부를 확인하세요.

출처 및 참고 자료

함께 보기 크로스 사이트 스크립팅(XSS, CWE-79): 동작 원리와 언어별 방어 방법 — flawopen.com Markdown 렌더링은 XSS로부터 안전한가요? Svelte는 기본적으로 XSS를 막을까? SVG 업로드는 XSS 위험이 있나요? Vue v-html XSS 위험과 안전한 사용법 WKWebView loadHTMLString은 신뢰할 수 없는 콘텐츠에 안전한가요? 반사형·저장형·DOM 기반 XSS는 어떻게 다른가요?