본문 바로가기
Computer/Computer Security

React 보안 체크리스트

by curious week 2025. 4. 1.

 

취약점 유형❗ XSS (Cross Site Scripting)

설명: 사용자 입력을 DOM에 직접 렌더링하면 악성 스크립트가 실행될 수 있음
방어전략:
✅ dangerouslySetInnerHTML 지양 or DOMPurify 사용
✅ 사용자 입력 escape
✅ Content-Security-Policy(CSP) 설정


취약점 유형❗ LocalStorage/SessionStorage에 민감정보 저장

설명: JS로 접근 가능한 저장소 → XSS에 노출되면 토큰 탈취 가능
방어전략:
✅ accessToken은 React 메모리나 HttpOnly 쿠키에 저장
❌ localStorage에 저장하지 않기


취약점 유형❗ CSRF (Cross-Site Request Forgery)

설명: 다른 사이트에서 요청을 보내도 브라우저가 쿠키를 자동으로 전송함
방어전략:
✅ SameSite=Strict 쿠키 설정
✅ 민감 요청은 반드시 POST로 처리
✅ Origin/Referer 헤더 체크


취약점 유형❗ Open Redirect

설명: ?redirect=/evil.com 등의 URL 파라미터를 통해 외부로 유도 가능
방어전략:
✅ redirect 대상은 whitelist 처리 (내부 경로만 허용)
✅ 외부 링크는 직접 입력 or 경고창 제공


취약점 유형❗ 콘솔 로그에 민감 정보 출력

설명: console.log(token) 같은 코드가 남아 있으면 브라우저 DevTools에서 노출 가능
방어전략:
✅ production 모드에서는 console.log 제거
✅ 로그 정리 및 환경별 분기 처리


취약점 유형❗ 인증 우회 (UI만 숨김)

설명: 버튼을 숨겨도 API가 열려 있으면 공격자는 직접 호출 가능
방어전략:
✅ 백엔드에서 항상 인증/인가 검증
❌ "admin 버튼 숨김"만으로 보호하지 않기


취약점 유형❗ axios 요청에 민감 정보 포함

설명: 토큰, 이메일, 비밀번호 등을 URL 쿼리로 보내면 로그/히스토리에 남음
방어전략:
✅ 민감 정보는 POST 요청 body에 포함
❌ GET /api?password=1234 방식 금지


취약점 유형❗ 외부 라이브러리 취약점

설명: 오래된 의존성이나 악성 패키지 사용 시 위험
방어전략:
✅ npm audit, yarn audit, snyk 등으로 점검
✅ 공식 패키지만 사용, 버전 주기적 업데이트


취약점 유형❗ 사용자 입력 검증 부족

설명: 클라이언트에서 필드 검증을 안 하면 백엔드에 잘못된 값 전달 가능
방어전략:
✅ zod, yup, react-hook-form 등으로 프론트에서 입력 검증
✅ 최소한의 유효성 검사는 프론트에서도 수행


취약점 유형❗ URL에 민감 정보 노출

설명: reset-password?token=... 같은 링크가 유출되면 위험
방어전략:
✅ URL에 토큰 담는 경우는 최소화 + 유효기간 짧게
✅ 가능하면 body나 쿠키에 담기


취약점 유형❗ 앱 크래시 유발 (비정상 입력으로)

설명: 타입 불일치 등으로 앱이 죽을 수 있음
방어전략:
✅ TypeScript로 타입 안정성 확보
✅ ErrorBoundary로 앱 전체 크래시 방지


React 개발 시 보안 좋은 습관

보안 개발 린트 사용 eslint-plugin-security 설치
HTTPS 강제 window.location.protocol !== 'https' → 리디렉션
CSP 설정 meta 태그 or Nginx 등에서 Content-Security-Policy 적용
정기 보안 테스트 OWASP ZAP 등으로 스캔
토큰 만료 타이머 accessToken 자동 만료 감지 & 리프레시 흐름 구현
devtools 접근 제한 (선택사항) dev 모드에서만 콘솔 허용 (if (process.env.NODE_ENV === "development"))

위험한 코드 vs 안전한 코드

❌ 위험한 코드

<div dangerouslySetInnerHTML={{ __html: userInput }} />
 
✅ 안전한 코드
import DOMPurify from 'dompurify'; 
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />

React 보안 마인드셋

프론트는 사용자가 “보는 화면”이 아니라,
공격자가 "조작할 수 있는 프로그램"이라고 생각해야 해요.

  • 항상 입력값은 조작될 수 있다고 가정
  • 백엔드는 항상 믿을 수 있어야 하고, 프론트는 철저히 방어적으로 개발

 

React + Rich Text Editor (RTE) XSS 방어 구조

시나리오

  • 사용자가 글쓰기 에디터에 <b>, <h1>, <script> 등 HTML을 입력함
  • 서버에 저장하고, 다른 사용자가 그 HTML을 그대로 출력
  • <script> 같은 악성 코드 실행 위험 → XSS!

사용 라이브러리

  • 에디터: react-quill, draft-js, toast-ui editor, tiptap, Slate 등
  • XSS 방어: DOMPurify (프론트), jsoup (서버)

프론트엔드 구조 (React)

import DOMPurify from 'dompurify';

const SafeHTMLViewer = ({ html }: { html: string }) => {
  return (
    <div
      dangerouslySetInnerHTML={{
        __html: DOMPurify.sanitize(html), // 중요!
      }}
    />
  );
};

왜 sanitize?

  • <script>, <img onerror>, onmouseover 같은 이벤트 기반 XSS를 자동 제거

서버 측도 더 안전하게 하려면?

Spring Boot + jsoup 예제

public class HtmlSanitizer {
    public static String sanitize(String html) {
        return Jsoup.clean(html, Safelist.basic()); 
        // basic()은 b, i, u, a, ul 등만 허용
    }
}

사용 예

String safeHtml = HtmlSanitizer.sanitize(userInputHtml);
article.setContent(safeHtml);

프론트에서도 DOMPurify, 백엔드에서도 jsoup.clean()
이중 방어


공격자 입장에서 자주 쓰는 XSS 페이로드

<script>alert(1)</script>
<img src=x onerror=alert(1)>
<a href="javascript:alert(1)">click me</a>
<div onclick="alert(1)">click</div>

이런 코드들이 들어와도 DOMPurify와 jsoup은 전부 제거해줘.


Markdown 게시판 XSS 방어 구조

시나리오

  • 사용자가 마크다운 작성
  • 서버에 저장된 Markdown → HTML로 변환해서 출력
    변환된 HTML이 위험할 수 있음

서버에서 XSS 방지 처리 흐름

### 입력 (Markdown)
안녕하세요! <script>alert('XSS')</script>

변환 + 정화

  1. Markdown → HTML (예: flexmark-java, markdown-it)
  2. 변환된 HTML을 jsoup.clean()으로 sanitize
  3. 안전한 HTML만 저장하거나 반환
String html = markdownToHtml(markdown); // markdown-it 등으로 변환
String safeHtml = Jsoup.clean(html, Safelist.basicWithImages());

클라이언트에서 safe하게 렌더링

<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />

보통은 서버에서 sanitize 후 전송하면 이건 생략 가능


요약 구조: Rich Text / Markdown 게시판 XSS 완전 방어 설계

[입력]
  React 에디터 (Quill, Toast UI 등)
    ↓ (HTML or Markdown 전송)
[서버]
  Markdown → HTML (필요 시)
  → Jsoup.clean(...)으로 sanitize
    ↓
[출력]
  React: DOMPurify.sanitize(...) → dangerouslySetInnerHTML

입력 시 escape❌, 출력 시 sanitize✅

클라이언트와 서버에서 이중 정화(sanitize) 구조!


보안 추가 팁

  • DOMPurify.sanitize(..., { USE_PROFILES: { html: true } }) 옵션도 고려
  • 사용자 이미지 업로드 시 파일 확장자 + Content-Type 이중 체크
  • MathJax, CodeBlock 등 마크다운 확장 기능은 화이트리스트를 따로 구성