CSP 적용 가이드, Report-Only부터 시작

핵심 요약
  • CSP는 브라우저가 불러올 수 있는 스크립트와 자원의 출처를 제한해 XSS 피해를 줄이는 보조 방어층이다.
  • 처음에는 Content-Security-Policy-Report-Only로 차단 없이 위반만 기록하고, 정책을 다듬은 뒤 정식 헤더로 바꾼다.
  • unsafe-inline은 보호 효과를 크게 낮추므로 nonce나 해시로 대체하고, 클릭재킹 방어는 frame-ancestors를 쓴다.

Content-Security-Policy(CSP)는 “이 페이지는 이 출처의 스크립트와 이미지만 불러올 수 있다”고 브라우저에 알려 주는 규칙입니다. 공격자가 페이지에 악성 스크립트를 끼워 넣는 데 성공하더라도 허용되지 않은 출처의 코드는 브라우저가 실행하지 않으므로, XSS(교차 사이트 스크립팅) 피해를 줄이는 효과가 있습니다.

효과가 큰 만큼 잘못 적용하면 정상 기능도 막힙니다. 그래서 CSP는 보안 헤더 중 가장 조심스럽게 도입해야 합니다. 이 글은 2026년 10월 24일 기준 MDN 문서를 확인해 시작 순서, 주요 지시어, 자주 막히는 지점을 정리했습니다. 다른 헤더는 보안 헤더 설정 방법 글에서 함께 볼 수 있습니다.

Report-Only로 먼저 시험하기

Content-Security-Policy-Report-Only 헤더는 정책을 강제하지 않고, 위반이 있으면 보고만 합니다. MDN은 정책을 시험하는 용도로 이 방식을 안내합니다. 사이트가 멀쩡히 동작하는 상태에서 어떤 자원이 위반으로 잡히는지 볼 수 있습니다.

Content-Security-Policy-Report-Only: default-src 'self'

브라우저 개발자 도구의 콘솔에도 위반 내용이 표시되므로 보고 수집 서버가 없어도 1차 점검은 할 수 있습니다. 보고를 서버로 받으려면 Reporting-Endpoints 헤더로 주소를 정하고 report-to 지시어로 연결합니다. MDN에 따르면 report-uri 는 더 이상 권장되지 않지만 report-to 를 지원하지 않는 브라우저를 위해 둘을 함께 보내라고 합니다.

Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports; report-to csp-endpoint

며칠에서 몇 주 동안 보고를 모아 정상 자원은 허용 목록에 추가하고, 의심스러운 자원은 원인을 찾습니다. 정리가 끝나면 헤더 이름만 Content-Security-Policy 로 바꿔 강제 적용합니다.

주요 지시어

MDN이 설명하는 지시어 중 소규모 사이트가 먼저 알아야 할 것들입니다.

지시어역할
default-src따로 정하지 않은 자원 종류의 기본 출처
script-src자바스크립트 출처
style-src스타일시트 출처
img-src이미지 출처
object-src<object>, <embed> 출처. 대부분 ‘none’ 으로 막는다
base-uri<base> 요소에 쓸 수 있는 주소 제한
frame-ancestors내 페이지를 프레임에 넣을 수 있는 상위 페이지

출처 표기에는 'none'(전부 차단), 'self'(같은 출처만), 특정 도메인 주소가 쓰입니다. 작은따옴표까지 값의 일부이니 빠뜨리지 마세요.

HSTS·CSP 같은 보안 헤더가 어떻게 설정되어 있는지 바로 확인할 수 있습니다.

보안 헤더 검사하기

unsafe-inline이 문제인 이유

HTML 안에 직접 적은 스크립트(인라인 스크립트)는 공격자가 끼워 넣은 스크립트와 구별되지 않습니다. 'unsafe-inline' 은 이를 모두 허용하므로 MDN도 쓰지 말라고 경고합니다. 이걸 넣는 순간 XSS를 막는 CSP의 핵심 효과가 크게 줄어듭니다.

대안은 두 가지입니다.

  • nonce. 서버가 응답마다 무작위 값을 만들어 CSP와 해당 <script> 태그에 같은 값을 넣습니다. 값이 맞는 스크립트만 실행됩니다. 요청마다 새로 만들어야 하며 고정값은 의미가 없습니다.
  • 해시. 인라인 스크립트 내용의 해시값을 정책에 넣어 해당 내용만 허용합니다. 내용이 바뀌면 값도 바뀝니다.

MDN에 따르면 지시어에 nonce나 해시가 함께 있으면 브라우저는 'unsafe-inline' 을 무시합니다. 그래서 점진적으로 전환하기 좋습니다. 워드프레스처럼 테마와 플러그인이 인라인 코드를 많이 쓰는 환경은 완전 차단이 어렵기 때문에, 일단 object-src 'none' 과 frame-ancestors 만 정해 두는 부분 적용도 현실적인 시작입니다.

frame-ancestors로 클릭재킹 막기

frame-ancestors 는 내 페이지를 <iframe> 으로 불러올 수 있는 상위 페이지를 정합니다. 옛 헤더 X-Frame-Options 와 같은 일을 하지만 더 유연합니다. MDN의 대응 관계는 다음과 같습니다.

X-Frame-OptionsCSP 동등 표현
DENYframe-ancestors ‘none’
SAMEORIGINframe-ancestors ‘self’
ALLOW-FROM (더 이상 쓰지 않음)frame-ancestors https://허용할-주소

이 지시어는 보고 전용 모드가 아니라 실제 헤더로 보내야 의미가 있다는 점에 주의하세요. 위 MDN 페이지는 이 제약을 따로 서술하지 않으므로, 적용 전에 명세나 MDN의 frame-ancestors 항목에서 확인하길 권합니다.

단계별 적용 예시

가장 단순한 출발 정책은 아래처럼 시작해 보고를 보며 늘려 가는 방식입니다. 주소는 예시이니 사이트가 쓰는 외부 서비스에 맞춰 바꾸세요.

Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'

Apache에서는 mod_headers 로 넣습니다.

Header always set Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'"

Nginx에서는 always 를 붙이고, 하위 블록에 다른 add_header 가 있으면 상위 설정이 상속되지 않는다는 점에 유의합니다.

add_header Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'" always;

Report-Only에서 위반이 충분히 줄었다면 같은 값을 Content-Security-Policy 로 바꾸어 적용하고, 문제가 생기면 곧바로 Report-Only로 되돌릴 수 있게 설정을 백업해 두세요. 설정을 반영할 때는 apachectl configtest 나 nginx -t 로 문법부터 확인합니다.

CSP의 한계와 보조 역할

OWASP XSS 방지 문서는 CSP를 단독으로 의존하지 말고 방어를 겹쳐 쓰는 용도로 설명합니다. XSS의 근본 방어는 출력 인코딩과 안전한 코드이며, CSP는 그 방어가 뚫렸을 때 피해를 줄이는 장치입니다. 기본 방어는 SQL 인젝션·XSS 방어 기초 글에서 다룹니다.

보안 헤더 검사HSTS·CSP·X-Content-Type-Options 등 6개 헤더의 값 평가을 무료로 확인할 수 있습니다.

보안 헤더 검사하기

자주 묻는 질문

CSP를 넣으면 사이트가 깨지나요?

정책이 사이트가 쓰는 스크립트, 스타일, 외부 서비스를 막으면 일부 기능이 동작하지 않을 수 있습니다. 그래서 Report-Only로 먼저 위반 내용을 확인한 뒤 적용하는 것이 안전합니다.

default-src 'self' 한 줄이면 충분한가요?

시작점으로는 좋지만 충분하지는 않습니다. 인라인 스크립트나 외부 서비스를 쓰는 사이트에서는 바로 깨질 수 있고, object-src, base-uri, frame-ancestors 같은 지시어를 별도로 정해 두는 편이 좋습니다.

meta 태그로 CSP를 넣어도 되나요?

MDN은 meta 요소로 정책을 지정하는 방식도 소개합니다. 다만 서버 헤더가 더 일관되게 적용되고, frame-ancestors 같은 일부 지시어는 헤더로 보내야 하는 것으로 알려져 있으니 가능하면 헤더를 쓰세요.

CSP만 있으면 XSS를 막을 수 있나요?

아닙니다. OWASP는 CSP를 단독 방어로 의존하지 말고 출력 인코딩 같은 기본 방어와 함께 쓰는 심층 방어 수단으로 설명합니다.

확인한 자료

기술 정보는 2026-10-24에 위 자료로 확인했습니다. 소프트웨어 버전·기본값·서비스 정책은 바뀔 수 있으므로 적용 전 각 문서의 최신 내용을 확인하세요. 이 글은 일반 정보이며 특정 서비스 가입을 권하지 않습니다.

Next
웹사이트 보안

보안 헤더 설정 방법, Apache·Nginx 예시

HSTS, CSP, X-Content-Type-Options, Referrer-Policy, X-Frame-Options, Permissions-Policy의 역할과 권장값, Apache·Nginx 설정 예시, 적용 후 확인 방법을 정리했습니다.

가이드 보기 →
웹사이트 보안

SQL 인젝션 XSS 방어 기초, 준비된 문장 활용

SQL 인젝션과 XSS를 막는 기본 방어를 정리했습니다. 준비된 문장, 허용 목록 검증, 최소 권한, 출력 이스케이프와 안전한 DOM 사용법, CSP의 보조 역할을 다룹니다.

가이드 보기 →
웹사이트 보안

HSTS 설정 방법, max-age와 단계적 적용

HSTS 헤더의 max-age, includeSubDomains, preload 의미와 Apache·Nginx 설정 예시, 5분에서 1년까지 단계적 적용 순서, preload 등록 전 주의점을 정리했습니다.

가이드 보기 →
웹사이트 보안

웹사이트 악성코드 감염 대처, 초기 대응과 증거 보존

웹사이트 악성코드 감염이 의심될 때 먼저 할 일을 순서대로 정리했습니다. 격리, 증거 보존, 비밀번호 교체, 정리와 재발 방지, Search Console 검토 요청까지 다룹니다.

가이드 보기 →
웹사이트 보안

홈페이지 해킹 확인 방법, Search Console 점검

내 홈페이지가 해킹됐는지 확인하는 순서를 정리했습니다. 검색 결과 스팸, 리디렉션, Search Console 보안 문제 보고서, 파일·로그 점검과 공개 검사의 한계까지 다룹니다.

가이드 보기 →