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-Options | CSP 동등 표현 |
|---|---|
| DENY | frame-ancestors ‘none’ |
| SAMEORIGIN | frame-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를 단독 방어로 의존하지 말고 출력 인코딩 같은 기본 방어와 함께 쓰는 심층 방어 수단으로 설명합니다.
확인한 자료
- MDN - Content-Security-Policy
- MDN - X-Frame-Options
- OWASP HTTP Headers Cheat Sheet
- OWASP Cross Site Scripting Prevention Cheat Sheet
- Apache HTTP Server - mod_headers
기술 정보는 2026-10-24에 위 자료로 확인했습니다. 소프트웨어 버전·기본값·서비스 정책은 바뀔 수 있으므로 적용 전 각 문서의 최신 내용을 확인하세요. 이 글은 일반 정보이며 특정 서비스 가입을 권하지 않습니다.