현대적인 웹 애플리케이션 개발에서 보안(Security)은 결코 선택 사항이 아닙니다. 특히 Node.js와 Express 환경은 전 세계적으로 가장 많이 사용되는 스택 중 하나인 만큼, 수많은 공격자의 표적이 되기도 합니다.
서비스를 배포한 후 "누가 내 사이트를 공격하겠어?"라는 생각은 자칫 위험한 결과로 이어질 수 있습니다. 자동화된 봇(Bot)들은 지금 이 순간에도 전 세계의 IP를 스캔하며 보안이 취약한 서버를 찾고 있습니다. 오늘은 실제 서비스 환경에서 필수적으로 적용해야 하는 보안 미들웨어의 핵심, Helmet.js와 Rate Limit에 대해 심도 있게 다루어 보겠습니다.
1. Helmet.js: 보이지 않는 방패, HTTP 헤더 보안
웹 브라우저와 서버가 통신할 때, HTTP 응답 헤더에는 수많은 정보가 담깁니다. 공격자들은 이 헤더 정보를 분석하여 서버의 소프트웨어 종류, 버전, 보안 정책 등을 파악합니다. Helmet은 이러한 위험 요소를 사전에 차단하는 역할을 합니다.
- Express의 기본 헤더 정보를 숨겨 서버 스택 노출을 방지합니다.
- 브라우저가 수행해야 할 보안 정책(CSP, HSTS 등)을 강제합니다.
- 클릭재킹(Clickjacking)과 같은 고전적이지만 치명적인 공격을 방어합니다.
주요 보안 기능 설명
- X-Powered-By 제거: 기본적으로 Express는
X-Powered-By: Express헤더를 보냅니다. 이는 해커에게 공격 경로를 알려주는 힌트가 됩니다. Helmet은 이를 즉시 제거하여 불필요한 정보 노출을 막습니다. - Content-Security-Policy (CSP): 허용되지 않은 외부 스크립트 실행을 차단하여 XSS 공격의 근원을 봉쇄하는 강력한 도구입니다.
- Strict-Transport-Security (HSTS): 브라우저가 오직 HTTPS를 통해서만 접속하도록 강제하여 데이터 패킷을 가로채는 중간자 공격(MITM)을 예방합니다.
// 설치: npm install helmet
const express = require("express");
const helmet = require("helmet");
const app = express();
// 기본 보안 헤더 세트 활성화
app.use(helmet());
// 특정 환경에 맞는 커스텀 CSP 설정 예시
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "trusted-scripts.com"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
})
);
2. Express-Rate-Limit: 비정상적인 접근 차단
특정 IP에서 짧은 시간 동안 수만 번의 요청을 보내 서버를 마비시키는 서비스 거부 공격(DoS)은 웹 서비스의 가용성을 크게 해칩니다. 또한, 관리자 페이지의 비밀번호를 알아내기 위해 무차별 대입(Brute Force)을 시도하는 경우도 빈번합니다.
Express-Rate-Limit은 특정 시간 동안 하나의 IP에서 보낼 수 있는 요청의 횟수를 제한하여 이러한 공격을 효과적으로 방어하는 미들웨어입니다.
효과적인 제한 전략
- 전역 제한: 모든 API 요청에 대해 기본적인 제한을 두어 서버 자원을 보호합니다 (예: 15분당 100회).
- 특정 경로 제한: 로그인, 회원가입 등 민감한 경로는 더욱 엄격한 임계값을 설정합니다 (예: 1시간당 5회).
- 사용자 경험 고려: 정상적인 사용자가 서비스를 이용하는 데 불편함이 없도록 트래픽 패턴을 분석하여 임계값을 설정하는 것이 중요합니다.
// 설치: npm install express-rate-limit
const rateLimit = require("express-rate-limit");
// 모든 요청에 대한 일반적인 제한 설정
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15분
max: 100, // IP당 최대 100회
message: "너무 많은 요청이 감지되었습니다. 잠시 후 다시 시도해 주세요.",
standardHeaders: true, // 응답 헤더에 RateLimit 정보 포함
legacyHeaders: false,
});
// 로그인 시도에 대한 강화된 제한 설정
const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1시간
max: 5, // IP당 최대 5회만 허용
message: "보안을 위해 1시간 동안 로그인이 제한됩니다.",
});
app.use("/api/", generalLimiter);
app.use("/api/login", loginLimiter);
3. 보안은 지속적인 관리의 영역입니다
보안은 한 번의 설정으로 완성되지 않습니다. 새로운 취약점은 언제나 발견될 수 있으며, 공격 기법 또한 진화합니다. 프로젝트를 안전하게 유지하기 위해서는 다음의 실천 사항을 권장합니다.
- 의존성 업데이트:
npm audit명령어를 활용하여 프로젝트에서 사용하는 패키지의 보안 결함을 주기적으로 점검하세요. - 환경 변수 보안: API 키, DB 비밀번호 등 민감한 정보는 소스 코드에 직접 노출하지 말고
.env파일이나 별도의 Secret Manager를 통해 관리해야 합니다. - 로깅 및 실시간 모니터링: 비정상적인 트래픽 폭증이나 에러 발생 시 즉시 감지할 수 있는 시스템을 구축하는 것이 사고 대응에 유리합니다.