Blog
Next.js에서 보안 헤더를 설정한다는 것의 의미와 한계
서론#
얼마 전 개인 블로그를 Security Headers를 통해 분석해보니,
보안 점수가 빨간불로 표시된 꽤 처참한 결과를 확인했다.
특별히 위험한 기능을 구현한 것도 아니고,
인증이 필요한 서비스도 아닌 단순한 블로그였지만
기본적인 보안 헤더조차 설정되어 있지 않았다는 점이 눈에 띄었다.
이 경험을 계기로,
“Next.js를 사용하면 보안은 어디까지 신경 써야 하는가?”
라는 질문을 다시 정리해보게 되었다.
왜 보안 헤더를 직접 설정해야 하는가?#
Next.js는 보안에 무관심한 프레임워크가 아니다.
다만, 보안과 관련된 많은 선택을 기본값으로 강제하지 않을 뿐이다.
이는 보안을 포기해서가 아니라,
서비스의 성격과 요구 사항에 따라 적용해야 할 정책이 다르기 때문이다.
즉, Next.js는
보안을 “자동으로 완성해주는 도구”라기보다는
어디까지를 프레임워크가 맡고,
어디부터를 개발자가 책임져야 하는지
경계를 명확히 드러내는 구조에 가깝다.
이 글에서 다루는 보안 옵션의 범위#
이 글에서는 Next.js에서 설정할 수 있는
대표적인 보안 헤더 몇 가지를 기준으로,
그 역할과 한계를 정리한다.
다만 이 글의 목적은
공격 기법을 상세히 설명하거나,
보안을 완성하는 방법을 제시하는 데 있지 않다.
대신 다음 질문에 집중한다.
- 이 보안 옵션들은 무엇을 막아주는가
- 그리고 무엇은 막아주지 않는가
Next.js에서 설정한 보안 헤더 예시#
아래는 개인 블로그에 적용한 보안 헤더 설정이다.
const securityHeaders = [
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
];
// 이 헤더들은 Next.js의 `next.config.js`에서 `headers()` 옵션을 통해 설정할 수 있다.
이 설정이 정답이거나,
모든 서비스에 그대로 적용해야 하는 기준은 아니다.
다만 Next.js 기준에서
“최소한 고려해볼 수 있는 보안 헤더”로는
무리가 없는 구성이다.
이제 각 보안 헤더가
무엇을 해주는지보다,
무엇을 해주지 않는지를 먼저 살펴보자.
각 보안 헤더는 무엇을 "막아주지 않는가"#
보안 헤더를 설명할 때 가장 흔한 실수는
“이 헤더를 쓰면 @@ 공격을 막을 수 있다”라고 단정하는 것이다.
이 섹션에서는 각 보안 헤더가 무엇을 막아주는지보다,
무엇을 책임지지 않는지를 기준으로 살펴본다.
4-1. X-Frame-Options#
X-Frame-Options는 페이지가 iframe 안에서 렌더링되는 방식을 제한한다.
이 설정을 통해,
다른 출처에서 내 페이지를 감싸는 형태의 UI 공격을 줄일 수 있다.
하지만 이 헤더는 다음을 보장하지 않는다.
- 인증을 보호해주지 않는다
- 권한 검증을 대신해주지 않는다
- 서버 요청 자체를 차단하지 않는다
즉, 이 옵션은
“누가 이 페이지를 볼 수 있는가”를 결정하지 않는다.
단지,
어떤 방식으로 화면이 노출될 수 있는가를 제한할 뿐이다.
참고로, X-Frame-Options는 최신 브라우저에서는
Content-Security-Policy: frame-ancestors로 대체되는 흐름에 있으며,
레거시 대응 목적이 아니라면 CSP 사용이 더 권장된다.
4-2. X-Content-Type-Options#
X-Content-Type-Options: nosniff는
브라우저가 파일의 실제 내용을 추측해
임의로 실행하는 동작을 막는다.
이 설정은 브라우저의 위험한 해석을 줄여주지만,
다음과 같은 역할을 하지는 않는다.
- XSS를 근본적으로 차단하지 않는다
- 악성 스크립트를 판별하지 않는다
- 서버 응답을 검증하지 않는다
즉, 이 헤더는
공격을 탐지하거나 차단하는 기능이 아니라
브라우저가 잘못된 선택을 하지 못하게 제한하는 옵션에 가깝다.
4-3. Referrer-Policy#
Referrer-Policy는
다른 출처로 이동할 때
얼마나 많은 요청 정보를 함께 전달할지를 제어한다.
이를 통해,
불필요한 URL 정보나 쿼리 스트링 노출을 줄일 수 있다.
하지만 이 설정 역시 다음을 보장하지 않는다.
- 외부 요청 자체를 막지 않는다
- 접근 권한을 제어하지 않는다
- 민감한 데이터 보호를 자동으로 수행하지 않는다
이 옵션의 목적은
보안을 강화하는 것이라기보다,
정보 노출을 최소화하는 것에 가깝다.
4-4. Permissions-Policy#
Permissions-Policy는
브라우저가 제공하는 기능에 대한 접근을 제한한다.
예를 들어,
카메라나 마이크, 위치 정보와 같은 기능을
명시적으로 비활성화할 수 있다.
하지만 이 설정은 다음과 무관하다.
- 서버 보안
- 인증 상태
- 사용자 권한 검증
즉, 이 옵션은
보안보다는 권한 최소화 원칙을 브라우저 수준에서 적용하는 도구에 가깝다.
이 보안 헤더들이 공통적으로 가지는 성격#
지금까지 살펴본 보안 헤더들은
모두 하나의 공통점을 가진다.
이 옵션들은:
- 요청을 검증하지 않는다
- 공격을 판별하지 않는다
- 권한을 부여하거나 차단하지 않는다
대신,
- 브라우저의 위험한 동작을 제한하고
- 공격 표면을 줄이며
- 실수로 보안을 깨뜨릴 가능성을 낮춘다
즉, 이 보안 헤더들은
보안을 제공한다기보다,
보안 사고가 발생하기 어려운 환경을 만든다고 보는 편이 정확하다.
Next.js 보안을 오해하기 쉬운 지점#
여기서 말하는 보안은
인증이나 권한 검증처럼
공격을 직접 차단하는 보안을 의미하지 않는다.
이 글에서 다루는 보안은,
공격이 성립하기 쉬운 환경을 미리 제거하는
브라우저 레벨의 보안이다.
정리#
이 글에서 다룬 보안 헤더들은
공격을 직접 차단하는 보안이 아니라,
공격이 성립하기 쉬운 환경을 미리 제거하는 장치들이다.
Next.js 보안은 옵션을 많이 켜는 것이 아니라,
각 옵션의 책임 범위를 정확히 이해하는 것에서 시작된다.
참고#
관련 게시글
5개
React는 왜 기본적으로 XSS에 강할까?
React가 XSS에 강해 보이는 이유를 렌더링 방식 관점에서 설명하고, dangerouslySetInnerHTML, 속성 기반 주입, DOM 직접 조작 시 다시 취약해지는 지점을 정리합니다.
NEXT_PUBLIC_ 환경변수는 왜 env인데 클라이언트에 노출될까
NEXT_PUBLIC_ 환경변수가 왜 클라이언트 번들에 포함되는지, env는 언제 비밀이 되고 언제 설정값이 되는지 Next.js 빌드 구조와 보안 기준으로 정리합니다.
innerHTML을 사용할 때 조심해야 하는 이유
innerHTML이 왜 보안상 주의가 필요한 API인지, XSS 취약점과 안전한 대체 방법(textContent, sanitize)을 통해 정리합니다.
env는 보안인가? 많은 개발자들이 착각하는 이유
env는 보안 기능일까? 이 글에서는 env의 역할과 한계, NEXT_PUBLIC_ 환경 변수의 노출 특성, 프론트엔드에서 노출돼도 되는 값의 기준을 정리합니다.
XSS란 무엇인가: 브라우저에서 실행되는 공격
XSS(Cross-Site Scripting)는 서버가 아닌 사용자의 브라우저를 공격하는 웹 보안 취약점입니다. 이 글에서는 XSS의 개념, 발생 원인, 주요 유형과 위험성을 정리합니다.