웹 보안 헤더 최소 구성 — 실무에서 먼저 적용해야 할 것들
보안 감사에서 헤더 하나 때문에 통과 못 했다. 아주 간단한 헤더들을 설정하면 점수가 올라간다.
필수 5가지 보안 헤더
1. Content-Security-Policy (CSP)
어떤 리소스를 로드할 수 있는지 제한:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'
의미:
default-src 'self': 같은 도메인에서만 로드script-src 'self' 'unsafe-inline': JS는 같은 도메인 + 인라인 (점진적 마이그레이션)style-src 'self': CSS는 같은 도메인만
개선 버전 (인라인 제거):
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self'; img-src 'self' data:; connect-src 'self' https://api.example.com
Nginx 설정:
server {
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
}
2. Strict-Transport-Security (HSTS)
항상 HTTPS 사용:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
의미:
max-age=31536000: 1년간 HTTPS 강제includeSubDomains: 서브도메인도 포함preload: 브라우저 HSTS 목록에 등록
Nginx:
server {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
3. X-Content-Type-Options
MIME 타입 스니핑 방지:
X-Content-Type-Options: nosniff
예시: 공격자가 .jpg로 된 JavaScript 파일을 올림 → 브라우저가 실행 가능
이 헤더로 방지됨.
4. X-Frame-Options
Clickjacking 공격 방지:
X-Frame-Options: SAMEORIGIN
또는:
X-Frame-Options: DENY # 모든 iframe 차단
5. Referrer-Policy
Referer 헤더 제어:
Referrer-Policy: strict-origin-when-cross-origin
의미: 같은 도메인에는 전체 URL 전송, 다른 도메인에는 도메인만 전송
전체 Nginx 설정
server {
listen 443 ssl http2;
server_name example.com;
# SSL 인증서
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# 보안 헤더
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "microphone=(), camera=()" always;
location / {
proxy_pass http://localhost:3000;
}
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Express.js 설정
helmet 라이브러리 사용 (권장):
npm install helmet
// app.ts
import helmet from 'helmet';
app.use(helmet());
// 커스텀
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'https://cdn.jsdelivr.net'],
styleSrc: ["'self'"],
},
}));
점수 개선
| 헤더 | 점수 |
|---|---|
| 헤더 없음 | 40/100 |
| 기본 5개 | 75/100 |
| CSP + 강화 | 85/100 |
| 완전 구성 | 95/100 |
실무 적용 순서 (운영 중 서비스 기준)
운영 서비스에서 가장 많이 실패하는 건 "한 번에 CSP를 강하게 적용"하는 방식이다. 실제로는 아래 순서가 안전하다.
- 1주차: HSTS, nosniff, X-Frame-Options, Referrer-Policy 먼저 적용
- 2주차: CSP를 Report-Only로 넣고 위반 로그 수집
- 3주차: 인라인 스크립트 제거, 외부 CDN 도메인 화이트리스트 정리
- 4주차: CSP 강제 모드로 전환하고 배포 체크리스트에 고정
특히 마케팅 스크립트나 분석 스니펫이 있는 사이트는 2주차 로그 분석이 필수다. 이 단계 없이 강제 모드로 가면 화면이 부분적으로 깨지는 장애가 난다.
배포 후 검증 체크리스트
curl -I https://your-domain.com로 응답 헤더 확인- 모바일/데스크톱 주요 페이지 5개 이상 실제 렌더링 점검
- 로그인, 결제, 문의 등 핵심 사용자 플로우 수동 테스트
- 브라우저 콘솔의 CSP 위반 로그 수집 및 도메인 정리
- 서브도메인 포함 여부 확인 후 HSTS
includeSubDomains유지
이 검증을 CI 파이프라인에 넣어두면 이후 릴리즈에서 헤더 누락이 재발하지 않는다.
결론
이 5개 헤더만 적용해도 보안 상태는 눈에 띄게 개선된다. 다만 핵심은 "설정"이 아니라 "운영 중 검증"이다. 점진 적용과 로그 기반 보완까지 완료하면, 실무 감사에서 반복적으로 지적받던 항목을 대부분 정리할 수 있다.