CSP(Content Security Policy)란 무엇인가?
웹 애플리케이션을 개발하다 보면 XSS(Cross-Site Scripting)와 같은 웹 보안 공격을 고려해야 한다.
공격자가 웹 페이지에 악성 JavaScript를 삽입하는 데 성공하면 사용자의 쿠키나 세션 정보가 탈취되거나, 사용자를 악성 사이트로 유도하는 등의 문제가 발생할 수 있다.
이러한 공격을 방어하기 위한 방법 중 하나가 CSP(Content Security Policy)다.
CSP는 브라우저가 웹 페이지에서 어떤 리소스를 어디에서 불러올 수 있는지 제한하는 보안 정책이다.
쉽게 말하면 서버가 브라우저에게 다음과 같은 규칙을 전달하는 것이다.
"이 페이지에서는 JavaScript를 이 출처에서만 실행해라."
"이미지는 이 출처에서만 가져와라."
"외부 iframe은 허용하지 마라."
브라우저는 서버가 전달한 CSP 정책을 기준으로 리소스의 로딩과 실행을 제한한다.
1. CSP가 필요한 이유
CSP를 이해하기 위해 대표적인 공격 방식인 XSS를 먼저 살펴볼 필요가 있다.
예를 들어 게시판의 댓글에 다음과 같은 악성 코드가 삽입됐다고 가정한다.
<script>
alert('악성 코드 실행');
</script>
웹 애플리케이션이 사용자 입력을 제대로 검증하지 않는다면 해당 스크립트가 다른 사용자의 브라우저에서 실행될 수 있다.
실제 공격에서는 단순한 alert() 대신 사용자의 정보를 탈취하거나 특정 요청을 실행하는 등의 악성 코드가 사용될 수 있다.
CSP를 적용하면 브라우저가 실행할 수 있는 JavaScript의 출처나 실행 방식을 제한할 수 있다.
예를 들어 다음과 같은 정책을 설정할 수 있다.
Content-Security-Policy: script-src 'self'
이 정책은 현재 페이지와 동일한 출처에서 제공되는 JavaScript만 실행하도록 제한한다.
따라서 외부에서 삽입된 악성 스크립트가 있더라도 CSP 정책에 의해 실행이 차단될 수 있다.
다만 CSP는 XSS를 완벽하게 해결하는 방법은 아니다.
입력값 검증과 출력 시 인코딩, 적절한 세션 관리 등 기본적인 보안 조치와 함께 사용하는 추가적인 방어 계층으로 보는 것이 적절하다.
2. CSP는 어떻게 동작하는가?
CSP는 일반적으로 HTTP 응답 헤더를 통해 브라우저에 전달한다.
Content-Security-Policy: default-src 'self'
브라우저는 HTTP 응답을 받은 후 CSP 정책을 확인하고 해당 정책에 따라 리소스의 로딩과 실행을 허용하거나 차단한다.
예를 들어 서버가 다음과 같은 CSP를 전달한다고 가정한다.
Content-Security-Policy: default-src 'self'
'self'는 현재 웹 페이지와 동일한 Origin을 의미한다.
따라서 기본적으로 동일한 Origin에서 제공되는 리소스만 허용하고 다른 출처의 리소스는 차단한다.
3. CSP의 주요 Directive
CSP는 여러 가지 Directive를 사용해 리소스별로 세부적인 정책을 설정할 수 있다.
대표적인 Directive는 다음과 같다.
Directive설명
| default-src | 다른 Directive에서 별도로 지정하지 않은 리소스의 기본 정책 |
| script-src | JavaScript 실행 출처를 제한 |
| style-src | CSS 스타일의 출처를 제한 |
| img-src | 이미지 리소스의 출처를 제한 |
| font-src | 폰트 리소스의 출처를 제한 |
| connect-src | AJAX, Fetch, WebSocket 등의 연결 대상을 제한 |
| media-src | 오디오, 비디오 등의 미디어 출처를 제한 |
| frame-src | iframe 등 프레임에서 로드할 수 있는 출처를 제한 |
| object-src | <object>, <embed> 등의 리소스 출처를 제한 |
| frame-ancestors | 현재 페이지를 iframe으로 삽입할 수 있는 출처를 제한 |
| base-uri | <base> 태그에서 사용할 수 있는 URL을 제한 |
| form-action | <form> 요청을 보낼 수 있는 대상을 제한 |
default-src
default-src는 CSP의 기본 정책을 설정하는 Directive다.
Content-Security-Policy: default-src 'self'
별도의 정책이 지정되지 않은 리소스는 default-src의 정책을 따른다.
예를 들어 다음과 같이 설정하면
Content-Security-Policy: default-src 'self'
기본적으로 동일한 Origin의 리소스만 허용한다.
다만 특정 Directive를 별도로 설정하면 해당 Directive가 우선한다.
Content-Security-Policy:
default-src 'self';
img-src 'self' https://images.example.com;
이 경우 일반적인 리소스는 'self'만 허용하지만 이미지는 images.example.com에서도 가져올 수 있다.
script-src
script-src는 JavaScript의 실행 출처를 제어한다.
Content-Security-Policy: script-src 'self'
현재 Origin에서 제공되는 JavaScript만 실행하도록 제한한다.
외부 CDN을 사용하는 경우에는 해당 출처를 명시할 수 있다.
Content-Security-Policy:
script-src 'self' https://cdn.example.com;
이 경우 현재 Origin과 cdn.example.com에서 제공되는 JavaScript를 허용한다.
style-src
style-src는 CSS 리소스의 출처를 제한한다.
Content-Security-Policy:
style-src 'self' https://cdn.example.com;
현재 Origin과 지정된 CDN의 CSS만 허용한다.
여기서 주의할 점은 Inline Style이다.
다음과 같은 코드는 CSP 정책에 따라 차단될 수 있다.
<div style="color: red;">
Hello
</div>
style-src 'self'만 설정한 경우 Inline Style을 허용하지 않는 것이 기본적인 보안 방향이다.
img-src
이미지를 가져올 수 있는 출처를 제한한다.
Content-Security-Policy:
img-src 'self' https://images.example.com;
현재 Origin과 지정된 이미지 서버에서 가져오는 이미지만 허용한다.
외부 이미지 서버를 사용하는 서비스라면 해당 도메인을 CSP에 추가해야 한다.
connect-src
connect-src는 JavaScript에서 서버와 통신할 수 있는 대상을 제한한다.
예를 들어 다음과 같이 설정할 수 있다.
Content-Security-Policy:
connect-src 'self' https://api.example.com;
이 경우 Fetch, XMLHttpRequest, WebSocket 등의 연결을 현재 Origin과 api.example.com으로 제한한다.
프론트엔드와 API 서버가 서로 다른 도메인으로 구성된 환경에서는 특히 중요하다.
frame-ancestors
frame-ancestors는 현재 페이지를 어떤 사이트에서 iframe으로 삽입할 수 있는지를 제한한다.
Content-Security-Policy:
frame-ancestors 'self';
이렇게 설정하면 동일한 Origin에서만 해당 페이지를 iframe으로 삽입할 수 있다.
아예 다른 사이트에서 iframe으로 삽입하는 것을 막으려면 다음과 같이 설정할 수 있다.
Content-Security-Policy:
frame-ancestors 'none';
이는 Clickjacking 공격을 방어하는 데 활용할 수 있다.
4. CSP에서 자주 사용하는 값
CSP에서는 몇 가지 특별한 값을 사용할 수 있다.
'self'
현재 페이지와 동일한 Origin을 의미한다.
script-src 'self'
현재 Origin의 스크립트만 허용한다.
'none'
모든 리소스를 허용하지 않는다.
object-src 'none'
object와 관련된 리소스를 모두 차단한다.
'unsafe-inline'
Inline Script 또는 Inline Style을 허용한다.
script-src 'self' 'unsafe-inline'
다만 unsafe-inline은 CSP의 보안 효과를 약화시킬 수 있으므로 가능한 한 사용하지 않는 것이 좋다.
'unsafe-eval'
JavaScript의 eval()과 같이 문자열을 코드로 평가하는 일부 기능을 허용한다.
script-src 'self' 'unsafe-eval'
이 역시 보안 수준을 낮출 수 있으므로 필요한 경우에만 사용하는 것이 좋다.
5. Nonce를 이용한 Script 허용
CSP에서는 Inline Script를 무조건 허용하는 대신 Nonce를 이용해 특정 Script만 허용할 수 있다.
서버에서 랜덤한 Nonce 값을 생성한다.
Content-Security-Policy:
script-src 'self' 'nonce-abc123';
HTML에서는 동일한 Nonce 값을 지정한다.
<script nonce="abc123">
console.log("허용된 스크립트");
</script>
브라우저는 CSP에 지정된 Nonce와 Script의 Nonce가 일치하는 경우 해당 Script의 실행을 허용한다.
중요한 점은 Nonce를 매 요청마다 예측하기 어려운 랜덤 값으로 생성해야 한다는 것이다.
따라서 실제 환경에서는 고정된 값을 사용하는 것이 아니라 서버에서 요청마다 새로운 Nonce를 생성하는 방식으로 구현한다.
6. Hash를 이용한 Script 허용
Nonce 외에도 Script의 내용을 기반으로 Hash를 생성해 특정 Inline Script를 허용할 수 있다.
예를 들어 다음과 같은 Script가 있다고 가정한다.
<script>
console.log("Hello");
</script>
해당 Script의 내용을 SHA-256 등의 방식으로 Hash한 후 CSP에 등록한다.
Content-Security-Policy:
script-src 'self' 'sha256-...';
브라우저는 실제 Script의 내용과 CSP에 지정된 Hash를 비교한다.
Hash가 일치하는 Script만 실행할 수 있다.
정적인 Inline Script를 허용해야 하는 경우 활용할 수 있는 방식이다.
7. CSP를 적용하는 방법
CSP는 가장 일반적으로 HTTP Response Header를 통해 설정한다.
예를 들어 Spring 환경에서는 응답 Header에 다음과 같은 정책을 추가할 수 있다.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://cdn.example.com;
img-src 'self' data:;
connect-src 'self' https://api.example.com;
object-src 'none';
frame-ancestors 'none';
프로젝트의 구조와 사용하는 외부 서비스에 따라 허용할 출처는 달라질 수 있다.
8. Meta 태그를 이용한 CSP
HTTP Header뿐만 아니라 HTML의 <meta> 태그를 통해서도 CSP를 설정할 수 있다.
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self';"
>
다만 일반적으로는 HTTP Response Header 방식이 더 권장된다.
Header 방식이 정책을 적용하는 데 더 적합하고, 모든 CSP 기능이 Meta 방식에서 동일하게 지원되는 것도 아니기 때문이다.
9. Report-Only 모드
CSP를 실제로 적용하기 전에 기존 서비스에 어떤 영향을 미치는지 확인하고 싶다면 Content-Security-Policy-Report-Only Header를 사용할 수 있다.
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
이 모드에서는 CSP 위반을 감지하지만 실제 리소스를 차단하지 않는다.
따라서 기존 서비스에서 어떤 리소스가 CSP 정책을 위반하는지 확인한 후 정책을 수정할 수 있다.
운영 중인 서비스에 CSP를 처음 적용할 때 유용한 방식이다.
10. CSP 적용 시 주의할 점
CSP는 강하게 설정한다고 무조건 좋은 것은 아니다.
기존 웹 애플리케이션이 다양한 외부 리소스를 사용하고 있다면 CSP를 적용하는 순간 정상적으로 동작하던 기능이 차단될 수 있다.
예를 들어 다음과 같은 서비스를 사용하고 있다면 각각의 출처를 CSP에 추가해야 할 수 있다.
- Google Analytics
- Google Fonts
- 외부 CDN
- 이미지 서버
- 지도 API
- 결제 모듈
- OAuth 로그인
- 외부 API
- WebSocket 서버
특히 기존 JSP 기반 애플리케이션처럼 Inline Script가 많이 존재하는 서비스에 CSP를 적용하면 예상보다 많은 수정이 필요할 수 있다.
다음과 같은 코드는
<script>
doSomething();
</script>
엄격한 CSP에서는 차단될 수 있다.
또한 다음과 같은 Inline Event Handler도 문제가 될 수 있다.
<button onclick="doSomething()">
클릭
</button>
따라서 CSP를 적용하기 전에 현재 애플리케이션에서 사용하는 Script, Style, API, 이미지, 외부 CDN 등의 출처를 먼저 파악하는 것이 좋다.
11. 좋은 CSP 정책은 어떻게 구성하는가?
CSP를 구성할 때는 가능한 한 허용 범위를 최소화하는 것이 중요하다.
예를 들어 다음과 같이 모든 출처를 허용하는 정책은
Content-Security-Policy:
default-src *
보안 측면에서 큰 의미가 없다.
대신 실제 서비스에서 사용하는 출처만 명시하는 것이 좋다.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://cdn.example.com;
img-src 'self' https://images.example.com;
connect-src 'self' https://api.example.com;
object-src 'none';
frame-ancestors 'none';
또한 가능하다면 다음과 같은 방식으로 보안 수준을 높일 수 있다.
- unsafe-inline 사용 최소화
- unsafe-eval 사용 최소화
- Inline Script 제거
- Nonce 또는 Hash 활용
- object-src 'none' 설정
- frame-ancestors 설정
- 실제 필요한 출처만 허용
- Report-Only로 사전 검증
12. CSP와 CORS의 차이
CSP와 CORS는 모두 웹 보안과 관련되어 있지만 목적이 다르다.
CSP는 브라우저가 어떤 리소스를 로드하거나 실행할 수 있는지를 제한하는 정책이다.
반면 CORS는 다른 Origin의 웹 페이지가 특정 서버의 리소스에 접근할 수 있는지를 제어하는 정책이다.
예를 들어
CSP
→ "이 페이지에서는 어느 서버의 JavaScript를 실행할 수 있는가?"
CORS
→ "다른 Origin의 JavaScript가 내 서버의 응답을 읽을 수 있는가?"
따라서 CSP와 CORS는 서로 대체하는 관계가 아니라 서로 다른 목적을 가진 보안 정책이다.
13. CSP와 XSS의 관계
CSP의 대표적인 목적 중 하나는 XSS 공격의 피해를 줄이는 것이다.
XSS 공격은 공격자가 악성 Script를 웹 페이지에 삽입하고 사용자의 브라우저에서 실행시키는 공격이다.
CSP는 브라우저가 실행할 수 있는 Script의 출처와 실행 방법을 제한함으로써 악성 Script가 실행되는 것을 어렵게 만든다.
따라서 CSP는 다음과 같은 구조로 이해하면 쉽다.
사용자 입력
↓
XSS 취약점 발생
↓
악성 Script 삽입
↓
브라우저에서 Script 실행 시도
↓
CSP 정책 확인
↓
허용되지 않은 Script라면 실행 차단
다만 CSP는 XSS 취약점 자체를 제거하는 것이 아니다.
따라서 다음과 같은 기본적인 보안 조치가 함께 필요하다.
입력값 검증
+
출력값 인코딩
+
안전한 세션/쿠키 관리
+
CSP
CSP는 이 중 하나의 방어 계층으로 보는 것이 적절하다.
14. 정리
CSP(Content Security Policy)는 웹 브라우저가 페이지에서 사용할 수 있는 리소스와 실행 가능한 Script를 제한하는 보안 정책이다.
특히 XSS 공격으로부터 사용자를 보호하기 위한 추가적인 보안 계층으로 활용할 수 있다.
CSP를 적용할 때는 단순히 다음과 같이 강한 정책을 적용하는 것보다
default-src *
실제 서비스에서 필요한 리소스만 명시적으로 허용하는 방식이 중요하다.
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://cdn.example.com;
img-src 'self' https://images.example.com;
connect-src 'self' https://api.example.com;
object-src 'none';
frame-ancestors 'none';
또한 기존 서비스에 적용할 때는 Content-Security-Policy-Report-Only를 이용해 먼저 정책 위반 사항을 확인한 후 실제 CSP를 적용하는 것이 안전하다.
결국 CSP는 "이 웹 페이지가 어떤 리소스를 신뢰하고 실행할 것인지 브라우저에 명시하는 보안 정책"이라고 이해하면 된다.
'Study > TIL' 카테고리의 다른 글
| [JPA] N+1 문제란? 발생 원리와 해결 방법 (0) | 2026.08.12 |
|---|---|
| [Kafka] Apache Kafka란? Producer, Consumer, Topic, Partition 핵심 개념 정리 (0) | 2026.08.04 |
| [Vault] HashiCorp Vault란? 안전한 Secret 관리 시스템 (0) | 2026.07.24 |
