모든 웹사이트가 전송해야 할 보안 헤더
서버를 잠그고, 프레임워크를 패치하고, 팀에 안전한 코딩을 교육했다고 해도, 올바른 HTTP 보안 헤더를 전송하지 않으면 사이트는 여전히 취약합니다. 이러한 응답 헤더는 브라우저가 콘텐츠를 처리할 때 어떻게 동작해야 하는지 알려주며, 누락되거나 잘못 구성된 헤더는 크로스 사이트 스크립팅(XSS), 클릭재킹, 프로토콜 다운그레이드 공격, 데이터 유출의 문을 열어둡니다.
이 가이드에서는 모든 웹사이트가 전송해야 할 필수 보안 헤더를 다루고, 각 헤더의 역할과 올바른 설정 방법을 설명합니다.
보안 헤더가 중요한 이유
보안 헤더는 사용자가 제어하는 브라우저 수준 정책을 강제하기 때문에 최전선 방어막입니다. 입력 검증이나 안전한 인증을 대체하지는 않지만, 공격 표면을 크게 줄여줍니다. 예를 들어, 엄격한 Content-Security-Policy는 공격자가 XSS 취약점을 발견하더라도 주입된 스크립트가 실행되는 것을 막을 수 있습니다.
주요 브라우저는 이러한 헤더를 일관되게 지원하며, 추가하는 데 보통 몇 줄의 설정만 필요합니다. 사용하지 않을 이유가 거의 없습니다.
필수 보안 헤더
아래는 모든 프로덕션 웹사이트가 전송해야 할 헤더입니다. 각 헤더의 역할, 권장 값, 일반적인 함정을 다룹니다.
1. Content-Security-Policy (CSP)
CSP는 XSS와 데이터 주입을 완화하는 가장 강력한 헤더입니다. 브라우저가 스크립트, 스타일, 이미지 및 기타 리소스를 로드할 수 있는 출처를 제한합니다. 좋은 시작 정책은 다음과 같습니다:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com; object-src 'none'; base-uri 'self'; form-action 'self';default-src 'self'로 시작하고 필요에 따라 예외를 점진적으로 추가하세요. 가능하면 스크립트에 'unsafe-inline'을 피하고 nonce나 해시를 사용하세요. 잘못 구성된 CSP는 사이트를 망가뜨릴 수 있으므로 철저히 테스트하세요.
2. HTTP Strict Transport Security (HSTS)
HSTS는 브라우저가 도메인에 대한 모든 향후 요청에 HTTPS를 사용하도록 강제합니다. 프로토콜 다운그레이드 공격과 쿠키 하이재킹을 방지합니다. 일반적인 헤더:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadmax-age는 초 단위입니다(1년). includeSubDomains는 정책을 모든 하위 도메인에 적용합니다. preload는 도메인을 브라우저 프리로드 목록에 추가할 수 있게 하지만, 모든 하위 도메인이 HTTPS를 지원한다고 확신할 때만 사용하세요.
3. X-Frame-Options
이 헤더는 사이트가 iframe에 포함되는 것을 방지하여 클릭재킹 공격을 막습니다. 사용법:
X-Frame-Options: DENY자체 콘텐츠를 프레이밍해야 한다면 SAMEORIGIN을 사용하세요. 최신 브라우저는 CSP의 frame-ancestors 지시문도 지원하며, 이는 더 유연합니다. CSP를 사용한다면 X-Frame-Options를 생략할 수 있지만, 둘 다 포함하면 더 나은 호환성을 제공합니다.
4. X-Content-Type-Options
이 헤더는 브라우저가 선언된 Content-Type과 다른 MIME 스니핑을 하는 것을 막습니다. 간단하고 효과적입니다:
X-Content-Type-Options: nosniff이것이 없으면 악성 파일이 실행 가능한 스크립트로 해석될 수 있습니다. 항상 설정하세요.
5. Referrer-Policy
Referrer-Policy는 요청과 함께 전송되는 리퍼러 정보의 양을 제어합니다. 균형 잡힌 기본값:
Referrer-Policy: strict-origin-when-cross-origin이것은 동일 출처 요청에는 전체 URL을, 교차 출처 요청에는 출처만 전송합니다. 분석을 유지하면서 민감한 경로 정보의 유출을 줄입니다.
6. Permissions-Policy
이전에 Feature-Policy였던 이 헤더는 지리적 위치, 카메라, 마이크와 같은 브라우저 기능을 활성화하거나 비활성화할 수 있게 합니다. 예:
Permissions-Policy: geolocation=(), camera=(), microphone=()사용하지 않는 기능을 비활성화하면 침해된 서드파티 스크립트의 영향을 줄일 수 있습니다.
보안 헤더 비교
| 헤더 | 목적 | 권장 값 |
|---|---|---|
| Content-Security-Policy | XSS 및 데이터 주입 완화 | default-src 'self'; script-src 'self' ... |
| Strict-Transport-Security | HTTPS 강제 | max-age=31536000; includeSubDomains |
| X-Frame-Options | 클릭재킹 방지 | DENY 또는 SAMEORIGIN |
| X-Content-Type-Options | MIME 스니핑 중지 | nosniff |
| Referrer-Policy | 리퍼러 유출 제어 | strict-origin-when-cross-origin |
| Permissions-Policy | 브라우저 기능 제한 | geolocation=(), camera=() |
보안 헤더 추가 방법
방법은 웹 서버나 프레임워크에 따라 다릅니다. 일반적인 접근 방식은 다음과 같습니다.
Nginx
서버 블록에 헤더를 추가하세요:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com; object-src 'none'; base-uri 'self'; form-action 'self';" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;always 매개변수는 오류 응답에서도 헤더가 전송되도록 합니다.
Apache
mod_headers를 활성화하고 추가하세요:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://images.example.com; object-src 'none'; base-uri 'self'; form-action 'self';"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"Node.js (Express)
기본적으로 많은 헤더를 설정하는 helmet 미들웨어를 사용하세요:
const helmet = require('helmet');
app.use(helmet());
// 필요에 따라 CSP 사용자 정의
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://trusted.cdn.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https://images.example.com"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
}
}));Helmet은 기본적으로 X-Content-Type-Options 및 Referrer-Policy와 같은 다른 헤더도 설정합니다.
헤더 테스트
배포 후 브라우저 개발자 도구(네트워크 탭) 또는 SecurityHeaders.com과 같은 온라인 스캐너를 사용하여 헤더를 확인하세요. 다음을 확인하세요:
- 모든 권장 헤더가 존재하는지.
- CSP가 정당한 리소스를 차단하지 않는지(콘솔에서 위반 확인).
- HTTPS가 완전히 작동할 때만 HSTS가 활성화되었는지.
- 오류 페이지에서도 헤더가 전송되는지.
사이트가 발전함에 따라 정기적으로 정책을 검토하고 업데이트하세요.
FAQ
가장 중요한 보안 헤더는 무엇인가요?
Content-Security-Policy는 가장 흔한 웹 취약점 중 하나인 XSS 및 데이터 주입 공격을 직접 완화하기 때문에 가장 중요한 것으로 간주됩니다.
보안 헤더가 다른 보안 조치를 대체할 수 있나요?
아니요. 보안 헤더는 심층 방어 계층입니다. 여전히 안전한 코딩, 입력 검증, 인증 및 기타 모범 사례가 필요합니다.
보안 헤더를 추가하면 웹사이트가 망가질 수 있나요?
잘못 구성하면, 특히 CSP가 정당한 리소스를 차단할 수 있습니다. 항상 스테이징 환경에서 테스트하고 프로덕션에 배포하기 전에 브라우저 콘솔에서 위반 사항을 모니터링하세요.
사이트를 잠글 준비가 되셨나요? 위의 헤더를 추가하는 것부터 시작하고 브라우저 개발자 도구로 테스트하세요. 보안 스캐너의 JSON 응답을 빠르게 검사하고 포맷하려면 JSON Formatter를 사용해 보세요.