OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 106 / 120
stack-analysis
CHAPTER 106 / 120
읽기 약 2
FUNCTION

XSS 방어: 출력 인코딩 + CSP + HttpOnly


핵심 개념

Reflected/Stored/DOM·React 자동·dangerouslySetInnerHTML — XSS 차단.

본문

XSS 3가지 종류

📋 코드 (13줄)
Reflected XSS:
- URL 파라미터에 스크립트
- 검색·에러 페이지에 입력값 반영
- 예: /search?q=<script>...</script>

Stored XSS:
- DB에 스크립트 저장
- 댓글·게시물 노출 시 실행
- 가장 위험

DOM-based XSS:
- 클라이언트 JS가 innerHTML 등에 입력값 직접 반영
- 서버 무관

React — 자동 escape

TSX📋 코드 (18줄)
// ✅ 안전 — React가 자동으로 텍스트로 처리
<div>{userInput}</div>
// → <script> 가 그대로 텍스트로 렌더


// ❌ 위험 — HTML로 해석
<div dangerouslySetInnerHTML={{ __html: userInput }} />


// ✅ 안전 — 필요할 때 sanitize
import DOMPurify from 'isomorphic-dompurify';

<div dangerouslySetInnerHTML={{
  __html: DOMPurify.sanitize(userInput, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
    ALLOWED_ATTR: ['href', 'title'],
  })
}} />

URL 파라미터 검증

TSX📋 코드 (30줄)
// ❌ 위험
function ProfilePage() {
  const params = useSearchParams();
  const name = params.get('name');
  return <h1>안녕하세요, {name}님</h1>;
  // ?name=<script>...</script>
  // → React 자동 escape로 안전
}


// ❌ 위험 — href에 직접
function Link() {
  const params = useSearchParams();
  const url = params.get('redirect');
  return <a href={url!}>이동</a>;
  // ?redirect=javascript:alert(1)
  // → JavaScript URL 실행 가능
}


// ✅ 안전 — 프로토콜 검증
function Link() {
  const params = useSearchParams();
  const url = params.get('redirect');

  if (url && !url.startsWith('/') && !url.startsWith('https://example.com/')) {
    return null;
  }
  return <a href={url!}>이동</a>;
}

마크다운 안전하게

TYPESCRIPT📋 코드 (25줄)
// react-markdown — 자동 sanitize
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

<ReactMarkdown
  remarkPlugins={[remarkGfm]}
  components={{
    // 외부 링크 안전
    a: ({ href, children }) => {
      if (href?.startsWith('http')) {
        return <a href={href} rel="noopener noreferrer" target="_blank">{children}</a>;
      }
      return <a href={href}>{children}</a>;
    },
  }}
>
  {userMarkdown}
</ReactMarkdown>


// 또는 marked + DOMPurify
import { marked } from 'marked';
import DOMPurify from 'isomorphic-dompurify';

const html = DOMPurify.sanitize(marked(userMarkdown));

Cookie HttpOnly + Secure

TYPESCRIPT📋 코드 (15줄)
// ❌ JS로 읽을 수 있는 쿠키
res.cookie('token', token);


// ✅ HttpOnly — JS 접근 불가
res.cookie('refreshToken', token, {
  httpOnly: true,
  secure: true,                // HTTPS만
  sameSite: 'strict',          // CSRF 방어
  maxAge: 7 * 86400 * 1000,
  path: '/',
});


// → XSS 발생해도 토큰 도난 X

CSP — XSS 마지막 방어선

📋 코드 (6줄)
script-src 'self' 'nonce-...';
// → 인라인 스크립트 차단 (nonce 없으면)
// → 외부 스크립트는 자기 도메인만


→ XSS 발생해도 스크립트 실행 차단

이메일·전화번호 등 입력

TYPESCRIPT📋 코드 (17줄)
// ✅ Zod로 형식 검증
const ProfileSchema = z.object({
  name: z.string().min(1).max(50),
  email: z.string().email(),
  phone: z.string().regex(/^\d{3}-\d{4}-\d{4}$/),
  bio: z.string().max(500),
  website: z.string().url().optional(),
});


// 저장 전 한 번 더 trim
const data = ProfileSchema.parse(req.body);
const sanitized = {
  ...data,
  name: data.name.trim(),
  bio: data.bio.trim(),
};

SVG 업로드 위험

TYPESCRIPT📋 코드 (10줄)
// ❌ SVG 그대로 표시 — 스크립트 실행 가능
<img src={svgUrl} />


// ✅ DOMPurify로 sanitize
const svg = await fetch(svgUrl).then(r => r.text());
const clean = DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true } });

// 또는 SVG 업로드 자체 금지
const ALLOWED_MIME = ['image/jpeg', 'image/png', 'image/webp'];

검증 도구

BASH📋 코드 (9줄)
# 정적 분석
pnpm dlx eslint-plugin-security


# OWASP ZAP — 자동 스캔
docker run -t owasp/zap2docker-stable zap-baseline.py -t https://example.com


# Burp Suite — 수동 침투 테스트

다음 챕터

CH.107 "CSRF 방어".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 코드의 XSS 방어 부분을 분석해서
실전 분석 + 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

XSS 방어 관련 베스트 프랙티스 5가지를
비교 분석해서 패턴 추출를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 XSS 방어
최적화 가능 위치를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 시장의 XSS 방어
트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
XSS 방어: 출력 인코딩 + CSP + HttpOnly 이 3가지만 확실히 잡으세요
1.React 자동 escape + DOMPurify로 사용자 HTML — 99% XSS 차단
2.Cookie httpOnly로 토큰 보호 — XSS 발생해도 도난 X
3.CSP nonce + strict-dynamic = 인라인 스크립트 마지막 방어선


공유하기
진행도 106 / 120