TechFeedTechFeed
Security

CSP, HSTS, X-Frame, Permissions-Policy | 배포 전 보안 헤더는 무엇을 켜나?

CSP·HSTS·X-Frame·Permissions-Policy 등 브라우저 보안 헤더를 배포 전 체크리스트로 정리한다. Report-Only 관측 후 enforce, nonce, 쿠키 Secure, Next.js headers·미들웨어 위치, curl·콘솔 검증 순서, OWASP·MDN 출처. 개발자·프론트엔드·백엔드·보안·API·CDN 실무 가이드.

by

프로덕션 주소에 콘텐츠 보안 정책(CSP)이 없으면, 악성 스크립트 한 줄이 사용자 브라우저에서 그대로 돈다. 전송 보안(HSTS)이 없으면 암호화되지 않은 접속이 한 번만 섞여도 쿠키가 평문으로 나갈 수 있다. 배포 전에 켤 응답 헤더는 열 개가 넘지 않는다. 표 한 장과 점검 목록만 채워도 보안 감사·침투 테스트 전에 “헤더 없음” 등급은 대부분 사라진다.


국내 서비스라도 CDN·클라우드 앞단을 쓰면 헤더를 앱이 쓰는지 엣지가 쓰는지부터 갈라야 한다. 권한·토큰 설계는 API 보안 체크리스트를, 비밀값·환경변수는 시크릿 프로덕션 체크리스트를, 앱 라우터 배포 전반은 Next.js 프로덕션 체크리스트를 같이 보면 빈칸이 줄어든다. 이 글 범위는 브라우저가 읽는 응답 헤더다.


배포 전 최소 세트 | 헤더 표 한 장

첫 스프린트에서 모든 정책을 완벽히 쓰지 않는다. 아래 여덟 줄을 “있음/없음”으로 먼저 찍는다. 값이 조금 틀려도 헤더 자체가 없는 상태보다는 낫다. 틀린 값은 스테이징에서 고치면 된다. 중요한 건 프로덕션 HTML 문서 응답에 최소한의 울타리가 보이느냐다.


헤더(약칭)역할 한 줄없으면 생기는 일
콘텐츠 보안 정책(CSP)스크립트·스타일·프레임 출처 제한인라인 스크립트 삽입·악성 외부 파일이 그대로 실행
전송 보안(HSTS)이후 접속을 암호화 채널로 강제중간자·혼합 콘텐츠 위험이 커짐
콘텐츠 타입 고정(nosniff)브라우저가 타입을 추측해 실행하지 못하게 함잘못된 타입 파일이 스크립트로 오인될 여지
프레임 차단타 사이트 안에 우리 화면을 끼워 넣기 차단투명 겹침 화면으로 클릭 유도(클릭재킹)
리퍼러 정책외부로 나가는 이전 주소 정보 범위 제한내부 경로·쿼리가 외부 로그에 남을 수 있음
권한 정책카메라·마이크·위치 등 기능 기본 차단임베드·외부 스크립트가 민감 기능을 요청 가능
오프너 격리다른 탭과의 창 참조 관계를 끊음(권장)탭 간 일부 공격면이 넓어짐
리소스 교차 읽기 제한다른 출처가 우리 리소스를 읽기 어렵게 함(권장)사이드 채널·리소스 탈취 완화가 약해짐

오픈웹 애플리케이션 보안 프로젝트(OWASP) 권장 묶음과 브라우저 문서(MDN)가 공통으로 꼽는 목록이다. 세부 문법 출처는 글 하단 참고 자료에 둔다. 교차 출처 자원 공유(CORS)도 응답 헤더지만, 브라우저 사전 요청 규칙이 달라 CORS 가이드를 따로 보는 편이 낫다.


실무에서 자주 하는 실수는 “API JSON에만 헤더를 넣고 HTML 문서에는 안 넣는 것”이다. 스크립트 삽입 피해는 대부분 문서 응답에서 난다. 반대로 JSON만 주는 순수 백엔드라면 콘텐츠 보안 정책의 체감 효과는 작고, 타입 고정·캐시·교차 출처 정책이 더 자주 쓰인다. 팀 문서에 “문서 응답 / API 응답” 두 줄로 정책을 나눠 적어두면 이후 PR 리뷰가 빨라진다.


한 줄 판정 — 헤더가 없는 배포는 “나중에 할 보안 작업”이 아니라 “기본 울타리 미설치”에 가깝다. 최소 세트부터 켠 뒤 정책을 조인다.


콘텐츠 보안 정책 작성 순서 | 관측 후 차단

콘텐츠 보안 정책을 하루 만에 “완벽한 화이트리스트”로 쓰려다 화면이 하얗게 되면, 팀은 헤더 자체를 끈다. 순서를 고정하는 편이 싸다. 보고만 하는 모드로 관측 → 위반 로그 약 1주 → 실제 차단 모드로 승격이 현장 감각에 맞다.


보고 전용 헤더 이름과 차단용 헤더 이름은 다르다. 처음 주에는 차단을 걸지 않고, 어떤 스크립트·연결이 막힐 뻔했는지 로그만 모은다. 로그에 “우리 도메인 / 광고·분석 / 예전 인라인 코드”를 세 갈래로 분류하면, 다음 주 허용 목록 회의가 짧아진다. 인라인 스크립트가 많으면 임의 허용(unsafe-inline)으로 도망가지 말고, 요청마다 바뀌는 일회용 값(nonce)이나 해시로 옮길 후보부터 이슈에 적는다.


기본 출처를 자기 도메인으로 두고, 스크립트·스타일·이미지·연결·글꼴·프레임 출처를 각각 채운다. 광고·분석 도메인은 “https 전체 허용” 같은 넓은 규칙 대신 호스트 단위로만 연다. 보고서 받는 주소가 있으면 소유 팀·보관 기간·개인정보 마스킹 규칙을 같이 적는다. 바이브코딩·자동 생성 HTML에 인라인 클릭 핸들러가 많으면 헤더 전에 마크업부터 정리한다. 관련 항목은 바이브코딩 보안 체크리스트에 있다.


프레임 조상 허용(frame-ancestors)을 정책 안에 넣으면 구형 프레임 차단 헤더와 역할이 겹친다. 둘 다 두어도 되고, 최신 브라우저는 프레임 조상 규칙을 우선한다. 폼 제출 목적지·base 태그 출처·플러그인 객체도 막아 두면 “문서 중간 삽입” 계열 사고가 줄어든다. 이미지 출처를 너무 넓히면 외부 추적 픽셀이 쉽게 들어온다. 아바타·미리보기 이미지가 많은 서비스만 예외를 두고, 가능하면 허용 저장소 호스트를 좁힌다.


브라우저 개발자 도구 네트워크 탭에서 응답 보안 헤더를 확인하는 화면
콘텐츠 보안 정책은 문서 응답 헤더에서 확인하고, 위반은 콘솔·리포트 주소로 모은다

전송 보안과 쿠키 | 암호화 전제부터

전송 보안 헤더는 “다음부터 암호화되지 않은 주소로는 시도하지 말라”는 브라우저 기억이다. 인증서가 자주 바뀌는 스테이징에 긴 유효 기간을 걸면, 팀원 브라우저가 며칠 동안 접속을 거부할 수 있다. 운영과 스테이징 정책을 반드시 나눈다.


운영 환경에서는 보통 1년 단위 기억 시간을 쓰고, 하위 도메인까지 포함할지는 인증서·서비스 준비 상태를 보고 정한다. 사전 등록 목록(preload)에 올리면 빼는 데 시간이 걸린다. 사내 도구·와일드카드 인증서가 흔들리는 도메인은 사전 등록을 미룬다. 세션 쿠키에는 암호화 채널 전용·스크립트 읽기 금지·적절한 동일 사이트 범위를 붙인다. 이름이나 경로에 비밀값을 넣지 말고, 토큰은 본문·인증 헤더 쪽으로 옮기는 계획을 세운다.


로드밸런서에서 암호화를 끝내고 앱에는 평문 포트로 넘기는 구성이면, 앱이 “이미 안전한 접속”으로 아는지 확인한다. 여기가 어긋나면 리다이렉트가 무한 반복되거나, 쿠키 보안 플래그가 빠진다. 페이지 안에 암호화되지 않은 이미지·스크립트가 남아 있는지도 콘솔에서 한 번 본다. 혼합 콘텐츠가 있으면 전송 보안만 켠 효과가 반감된다.


주의 — 전송 보안 사전 등록 목록에 올리면 철회에 시간이 걸린다. 인증서가 불안정한 도메인은 운영 헤더만으로도 충분하다.


클릭재킹·임베드 | 프레임 정책을 하나로

결제·설정·관리자 화면이 다른 사이트 프레임 안에 들어가면, 사용자는 우리 버튼을 누르는 줄 알고 실제로는 공격자 레이어를 누를 수 있다. 위젯·미리보기처럼 임베드가 제품 기능인 경우만 예외 출처를 연다. 예외 없이 “전체 허용”은 쓰지 않는다.


기본값은 프레임 안에 넣지 않음, 또는 같은 출처만 허용이다. 구형 클라이언트를 고려하면 전통적인 프레임 옵션 헤더를 같이 둘 수 있다. 값이 서로 모순되면(한쪽은 허용, 한쪽은 차단) 디버깅이 길어진다. 파트너 임베드가 필요하면 허용 호스트를 명시하고, 임베드 전용 경로만 완화한다. 그 경로에는 로그인 세션 쿠키가 붙지 않게 경로·동일 사이트 속성을 분리한다.


관리자·결제·비밀번호 변경 주소는 예외 없이 프레임 차단이 맞다. 외부 로그인 팝업 흐름이 있으면 창 참조 격리 정책과 함께 본다. 인증 흐름 자체는 OAuth 실무 가이드 쪽 체크와 맞추면 빈칸이 줄어든다.


웹 애플리케이션 배포 파이프라인과 보안 설정 점검 체크리스트
프레임 정책은 제품 임베드 요구가 생기기 전에 기본 차단으로 두는 편이 싸다

타입 고정·리퍼러·권한 | 자주 빠지는 세 줄

콘텐츠 보안 정책과 전송 보안만 넣고 끝내면, 감사 리포트에 같은 잔여 항목이 반복된다. 아래 세 줄은 설정 비용이 낮고 효과가 분명하다. 전역 기본값으로 넣고, 기능이 있는 페이지만 예외를 연다.


콘텐츠 타입 고정은 문서·API·정적 파일 응답에 공통으로 건다. 리퍼러 정책은 외부로 나가는 이전 주소 정보를 줄여, 내부 경로가 광고·분석 로그에 남는 일을 막는다. 권한 정책은 카메라·마이크·위치·결제 등 쓰지 않는 기능을 기본 차단한다. 화상 상담·지도 첨부처럼 꼭 필요한 화면만 해당 출처에 기능을 연다.


사용자가 파일을 올리는 서비스라면 서버가 타입을 결정하고, 실행 가능 확장자는 저장소·배포망에서 막는다. 다운로드 응답에는 저장 파일 이름과 올바른 타입을 명시해 브라우저가 스크립트로 오인하지 않게 한다. 업로드·의존성 쪽 공급망 이슈는 Supply Chain 방어 가이드와 연결해 본다.


앱·엣지 어디에 넣나 | 진실 공급원은 하나

헤더를 앱·CDN·웹서버에 동시에 쓰면 값이 덮이거나 중복되어 디버깅이 길어진다. “최종으로 브라우저에 나가는 줄”의 주인을 하나만 고른다. 국내 트래픽이 많은 서비스도 해외 CDN을 앞에 두는 경우가 많아, 원본 서버 설정만 보고 안심하면 안 된다. 실제 사용자 응답을 한 번 덤프해 확인한다.


Next.js 계열이면 설정 파일의 공통 헤더 함수 또는 미들웨어에서 문서 HTML에 squirt 넣는다. 경로 매처로 API와 페이지를 나눌 수 있다. Node 서버 프레임워크면 전역 훅 한 곳에 모은다. 헬퍼 라이브러리를 쓰면 기본값을 읽고 팀에 맞게 덮어쓴다. 정적 사이트·리버스 프록시면 웹서버 설정이 롤백이 쉽다. CDN 엣지 규칙으로 강제할 때는 원본 헤더와 충돌 테스트를 반드시 한다.


요청마다 바뀌는 일회용 값(nonce) 기반 정책은 순수 정적 헤더 파일만으로는 부족하다. 미들웨어나 서버 렌더 레이어가 필요하다. 미리보기(PR) 환경에도 같은 헤더를 켜 두면 “운영만 깨짐”을 줄인다. 배포 순서·헬스체크 흐름에 “헤더 스모크 한 줄”을 붙이려면 프로덕션 배포 체크리스트와 같은 템플릿에 칸을 하나 추가하면 된다.


개발자가 서버 설정 파일과 보안 헤더 정책을 검토하는 장면
헤더는 앱·CDN·웹서버 중 한 레이어를 주인으로 정하고 나머지는 비운다

스테이징 검증 순서 | 덤프·브라우저·리포트

헤더 PR은 “설정 파일 커밋”으로 끝내지 않는다. 배포 후 5분 점검 또는 자동 잡에 아래 순서를 붙인다. 홈·로그인·핵심 API 세 경로의 응답 헤더 덤프를 저장해 두면, 나중에 “언제 빠졌는지” 비교가 가능하다.


브라우저 개발자 도구에서 문서 응답을 열어 콘텐츠 보안 정책·전송 보안 값이 보이는지 확인한다. 콘솔에 정책 위반이 쏟아지면, 알려진 서드파티인지 우리 코드인지 먼저 가른다. 로그인 → 핵심 저장 → 결제(또는 동등한 쓰기) 한 바퀴를 사람이 눌러 본다. 버튼이 죽은 경우가 대부분 스크립트 차단이다.


광고·채팅·결제 위젯이 막히면 바로 광범위 허용으로 풀지 말고, 필요한 호스트만 보안 리뷰 후 추가한다. 외부 보안 헤더 점수 사이트나 자체 스크립트로 전후 등급을 남긴다. 보고 전용 단계라면 리포트 주소에 샘플이 들어오는지 확인한다. 컨테이너 이미지·의존성 스캔은 별 트랙이다. 이미지 쪽은 이미지 스캔 가이드를 본다.


자주 깨지는 패턴 | 피하면 되는 여섯 가지

도구 점수만 맞추려고 넣은 설정이 오히려 보호를 약화시키는 경우가 있다. 아래는 점검표에 “금지” 칸으로 고정해 두는 편이 낫다.


스크립트 출처에 전체 허용이나 “모든 https”를 넣으면 점수는 올라가 보여도 삽입 공격 완화 효과가 거의 없다. 보고 전용 모드만 수개월 방치하면 차단은 영원히 안 온다. 달력에 승격일을 박는다. 인라인·동적 실행 임의 허용을 “임시”로 남기면 그게 기본값이 된다. 이슈 번호와 제거 분기를 적는다.


API에만 헤더를 넣고 HTML 문서에는 없는 구성, 스테이징에 긴 전송 보안 기억 시간, 디버그 로그에 인증 헤더를 찍는 습관도 반복된다. 주입·인증 문제는 헤더만으로 끝나지 않는다. 웹 취약점 상위 목록 점검은 OWASP 체크리스트와 병행한다.


범위 고정 — 이번 스프린트 목표는 “최소 여덟 헤더 존재 + 콘텐츠 보안 정책 보고 모드 1주”면 충분하다. 완벽한 화이트리스트는 다음 스프린트로 옮긴다.


2주 도입 순서 | 관측 후 차단

보안 헤더 작업은 범위가 커 보이지만, 문서 한 장과 풀 리퀘스트 두 개로 끝낼 수 있다. 처음부터 전사 표준을 만들지 말고, 대표 서비스 하나·문서 응답부터 닫는다. 2주 캘린더는 아래처럼만 맞춘다.


첫째 날에는 운영 응답을 덤프해 없는 항목과 주인 레이어(앱인지 CDN인지)를 정한다. 그다음 이틀은 타입 고정·리퍼러·프레임·권한 기본값을 올린다. 중반에는 운영에만 긴 전송 보안을 넣고 쿠키 플래그를 감사한다. 후반 일주일은 콘텐츠 보안 정책을 보고 모드로 돌리고 위반을 분류한 뒤, 인라인 정리와 최소 호스트 추가를 끝낸다. 마지막 이틀에 차단 모드로 올리고 롤백 방법을 런북에 세 줄 적는다. 네트워크 접근 통제·장비 정책은 별 트랙이다. 필요하면 제로 트러스트 가이드를 본다.


참고 자료


내부 연계: API 보안 체크리스트 · 시크릿·환경변수 · Next.js 프로덕션 · OWASP 상위 목록 · CORS · 백엔드 배포 체크리스트 · 컨테이너 이미지 스캔


자주 묻는 질문

콘텐츠 보안 정책과 웹 방화벽 중 무엇을 먼저 넣나?

역할이 다르다. 콘텐츠 보안 정책은 브라우저 안에서 스크립트 출처를 제한하고, 웹 방화벽은 네트워크 앞에서 요청 패턴을 걸러 낸다. 소규모 팀이면 응답 헤더 최소 세트(보고 모드 포함)가 설정 비용 대비 효과가 빠르다. 트래픽·공격면이 크면 방화벽을 병행한다.


Next.js에서 일회용 값 기반 정책을 쓰려면 미들웨어가 필요한가?

요청마다 다른 일회용 값을 넣으려면 정적 공통 헤더 설정만으로는 부족하다. 미들웨어나 서버 쪽에서 값을 만들고, HTML 스크립트 태그와 응답 헤더에 같은 값을 심어야 한다. 인라인 스크립트가 거의 없다면 해시 방식이나 외부 파일만 허용하는 정책으로 단순화할 수 있다.


전통 프레임 옵션과 프레임 조상 규칙을 둘 다 써야 하나?

최신 브라우저는 콘텐츠 보안 정책의 프레임 조상 규칙을 본다. 구형 클라이언트·일부 프록시를 고려하면 전통 프레임 옵션을 같이 두는 팀이 많다. 값이 서로 모순되지 않게(예: 둘 다 차단) 맞춘다.


광고·분석 스크립트 때문에 정책을 풀어야 하나?

전면 해제 대신 해당 호스트만 스크립트·연결·이미지 출처에 추가한다. 태그 관리 도구 한 곳이 임의의 스크립트를 불러오면 정책 이점이 크게 줄어든다. 마케팅 태그의 임의 주입 권한을 제품·보안이 같이 제한하는 편이 낫다.


API 전용 백엔드에도 브라우저 보안 헤더가 필요한가?

JSON만 제공해도 타입 고정·캐시·교차 출처 관련 헤더는 의미가 있다. 콘텐츠 보안 정책은 HTML을 거의 안 주면 효과가 제한적이다. 브라우저가 직접 부르는 주소와 문서 응답이 있는 게이트웨이를 구분해 정책을 나눈다.


헤더를 넣었더니 로그인이 깨졌다. 어디에 롤백 포인트를 두나?

CDN·엣지에서 켠 경우 엣지 규칙 비활성화가 가장 빠르다. 앱 설정이면 이전 배포로 돌리거나, 환경 변수 플래그로 차단 모드를 보고 모드로 내린다. 런북에 “헤더 플래그 위치”를 세 줄로 적어 둔다.


CSPHSTS보안 헤더X-Frame-OptionsPermissions-PolicyXSSNext.js보안개발자프론트엔드APICDNOWASP

함께 보면 좋은 문제 해결

EXPLORE / Security

이어서 읽어보기

전체 토픽 둘러보기