개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Express 보안 — XSS, CSRF, SQL Injection 방어

2025년 2월 11일
Express보안HelmetCORSRate Limit

Express 앱은 기본 설정만으로는 XSS, CSRF, SQL Injection 같은 공격에 무방비 상태다. 보안은 나중에 추가하는 옵션이 아니라 처음부터 설계에 포함해야 하는 요소다. 이 글은 실무에서 바로 적용할 수 있는 Express 보안 레이어를 단계별로 다룬다.

보안 레이어 구조

Client Request
     │
     ▼
┌─────────────┐
│   Helmet    │  ← HTTP 헤더 강화
└─────────────┘
     │
     ▼
┌─────────────┐
│    CORS     │  ← 출처 검증
└─────────────┘
     │
     ▼
┌─────────────┐
│ Rate Limit  │  ← 요청 수 제한
└─────────────┘
     │
     ▼
┌─────────────┐
│  Sanitize   │  ← 입력값 정제 (XSS/SQLi)
└─────────────┘
     │
     ▼
  Route Handler

Helmet — HTTP 헤더 강화

Helmet은 보안 관련 HTTP 헤더를 자동으로 설정하는 Middleware다. X-Powered-By 노출 차단, Content-Security-Policy 설정 등을 한 번에 처리한다.

npm install helmet
import express from "express";
import helmet from "helmet";

const app = express();

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
      },
    },
  })
);

CSP(Content-Security-Policy): 브라우저가 로드할 수 있는 리소스 출처를 제한해 XSS 공격을 방어하는 헤더.

CORS 설정

출처를 명시적으로 허용하지 않으면 * 와일드카드 허용 상태가 되어 CSRF 공격에 취약해진다.

import cors from "cors";

const allowedOrigins = ["https://myapp.com", "https://admin.myapp.com"];

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin || allowedOrigins.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error("Not allowed by CORS"));
      }
    },
    credentials: true,
  })
);

credentials: true 설정 시 Access-Control-Allow-Origin에 *를 사용할 수 없으며, 반드시 명시적 출처를 지정해야 한다.

Rate Limit — 무차별 대입 방어

로그인 엔드포인트에 Rate Limit이 없으면 Brute Force 공격에 그대로 노출된다.

import rateLimit from "express-rate-limit";

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15분
  max: 10,
  message: { error: "Too many login attempts. Try again later." },
  standardHeaders: true,
  legacyHeaders: false,
});

app.post("/auth/login", loginLimiter, loginController);

XSS 방어 — 입력값 정제

사용자 입력을 DB에 저장하기 전에 HTML 태그를 제거해야 한다.

import { JSDOM } from "jsdom";
import createDOMPurify from "dompurify";

const { window } = new JSDOM("");
const DOMPurify = createDOMPurify(window);

function sanitize(input: string): string {
  return DOMPurify.sanitize(input, { ALLOWED_TAGS: [] });
}

app.post("/comments", (req, res) => {
  const clean = sanitize(req.body.content);
  // clean을 DB에 저장
});

SQL Injection 방어 — Parameterized Query

문자열 접합으로 쿼리를 만드는 것은 가장 흔한 실수다. Parameterized Query를 반드시 사용해야 한다.

// 위험 — 절대 사용 금지
const query = `SELECT * FROM users WHERE email = '${email}'`;

// 안전 — Parameterized Query
const result = await db.query(
  "SELECT * FROM users WHERE email = $1",
  [email]
);

실무 보안 체크리스트

항목도구적용 범위
HTTP 헤더 강화Helmet전역 Middleware
출처 제한CORS전역 Middleware
요청 수 제한express-rate-limit인증 엔드포인트
XSS 정제DOMPurify사용자 입력 처리
SQL Injection 방어Parameterized Query모든 DB 쿼리
HTTPS 강제HSTS 헤더 (Helmet)프로덕션 전체

HSTS는 Helmet의 strictTransportSecurity 옵션으로 활성화되며, 브라우저가 이후 요청을 자동으로 HTTPS로 업그레이드하도록 강제한다.

← 이전 글Express Middleware — 실무에서 자주 사용하는 라이브러리
다음 글 →Express 성능 최적화 — 압축, 캐시, 클러스터링