이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- SameSite와 CSP는 각각 어떤 공격면을 줄이는가?
- 보안 헤더를 “있다/없다”가 아니라 응답 계약으로 관리하려면 무엇을 봐야 하는가?
- CSP를 report-only에서 enforce로 전환할 때 어떤 로그와 테스트가 필요한가?
개요
SameSite는 쿠키 전송 정책이다.
CSP는 Content Security Policy로, 브라우저가 어떤 리소스와 script를 로드하거나 실행할 수 있는지 제한하는 정책이다.
둘은 서로 다른 문제를 다룬다. SameSite는 CSRF 위험을 줄이고, CSP는 XSS 성공 후 실행 가능한 범위를 줄인다.
보안 헤더는 프론트 장식이 아니라 서버 응답 계약이다. API, HTML, admin, static asset 응답별로 필요한 헤더가 다를 수 있다.
공격 시나리오
서비스가 다음 헤더를 반환한다.
Set-Cookie: SESSION=opaque; SameSite=None; Secure
Content-Security-Policy: default-src *; script-src * 'unsafe-inline' 'unsafe-eval'SameSite가 None이라 cross-site 요청에 session cookie가 전송될 수 있다. CSP는 너무 넓어 XSS가 성공했을 때 외부 script 실행을 거의 제한하지 못한다.
공격자는 CSRF로 이메일 변경을 시도하고, 별도 저장 XSS 지점에서는 외부 script를 로드해 사용자 행동을 훔친다.
취약한 요청/응답 예시
HTTP/1.1 200 OK
Set-Cookie: SESSION=abc; Path=/; SameSite=None
Content-Security-Policy: default-src *; script-src * 'unsafe-inline'
Content-Type: text/html문제는 헤더가 없다는 것이 아니라, 헤더가 있어도 정책이 너무 넓다는 점이다.
주제별 핵심 판단
| 값 | 의미 | 주의점 |
|---|---|---|
Strict | cross-site 요청에 거의 전송하지 않는다 | 로그인 callback, 외부 링크 UX가 깨질 수 있다 |
Lax | top-level safe navigation 등 일부 전송 | 많은 서비스의 기본값으로 적합하지만 완전한 CSRF 방어는 아니다 |
None | cross-site 전송 허용 | Secure가 필요하고 CSRF 위험을 별도로 봐야 한다 |
인증 session cookie는 Lax 또는 Strict를 우선 검토한다. OAuth callback, 결제 redirect, 임베드 요구사항이 있으면 예외를 문서화한다.
개선된 요청/응답 예시
좋은 CSP는 짧고 명확하게 시작한다.
CSP 적용 기준은 다음처럼 좁게 시작한다.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'자주 확인할 directive는 다음과 같다.
default-src: 기본 리소스 출처script-src: script 로딩과 실행object-src: plugin 계열 차단base-uri: base tag 악용 제한frame-ancestors: clickjacking 방어img-src,connect-src,style-src: 실제 프론트 리소스 요구 반영
unsafe-inline, unsafe-eval, *는 방어 가치를 크게 낮춘다. 필요한 경우 이유와 제거 계획을 남긴다.
방어 설계
응답 유형별 보안 헤더 계약을 둔다.
HTML app:
Set-Cookie: HttpOnly; Secure; SameSite=Lax
Content-Security-Policy: default-src 'self'; ...
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
admin app:
SameSite=Strict 우선 검토
frame-ancestors 'none'
더 좁은 connect-src
JSON API:
Content-Type: application/json
X-Content-Type-Options: nosniff
CORS는 별도 allowlistCSP는 바로 enforce하기보다 Content-Security-Policy-Report-Only로 violation을 수집하고, 필요한 리소스를 정리한 뒤 차단 정책으로 전환한다.
Spring/HTTP 예시
@Bean
SecurityFilterChain headers(HttpSecurity http) throws Exception {
return http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives(
"default-src 'self'; " +
"script-src 'self' 'nonce-{nonce}'; " +
"object-src 'none'; " +
"base-uri 'self'; " +
"frame-ancestors 'none'"))
.contentTypeOptions(Customizer.withDefaults())
.referrerPolicy(referrer -> referrer
.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)))
.build();
}실제 nonce 치환은 view rendering 계층과 함께 설계해야 한다. 고정 문자열 nonce는 의미가 없다.
쿠키는 응답에서 직접 확인한다.
Set-Cookie: __Host-SESSION=opaque; Path=/; HttpOnly; Secure; SameSite=Lax운영 로그와 감사 지점
level=WARN event=csp_violation_reported result=observed document_uri=/dashboard directive=script-src blocked_uri=https://evil.example source_file=/dashboard trace_id=82ac01d1level=WARN event=security_header_misconfigured result=blocked header=Set-Cookie cookie=SESSION reason=samesite_none_without_secure environment=prod trace_id=82ac01d2CSP report는 잡음이 많을 수 있다. 확장 프로그램, 오래된 asset, 실제 공격 후보를 분류하는 운영 기준이 필요하다.
실전 판단 기준
- session cookie에
HttpOnly,Secure, 적절한SameSite가 있는가? - SameSite는 CSRF 위험을 줄이지만 인증/인가 검사를 대신하지 않는다.
- CSP는 실제 script 로딩 경로와 nonce/hash 운영 방식을 확인해야 효과가 있다.
테스트 포인트
SameSite=None쿠키에는Secure가 있는가?- CSP에
object-src 'none',base-uri 'self',frame-ancestors가 있는가? unsafe-inline,unsafe-eval,*가 있으면 사유와 제거 계획이 있는가?- CSP report-only 로그를 보고 enforce 전환 절차를 거쳤는가?
- HTML, admin, API 응답별 헤더 계약이 테스트되는가?
위험 신호!
- 보안 헤더 존재 여부만 체크하고 값의 의미를 보지 않는다.
- 모든 cookie에 일괄
SameSite=None을 준다. - CSP가
default-src *와unsafe-inline으로 사실상 열려 있다. - nonce가 요청마다 바뀌지 않는다.
- report-only를 영구 운영하고 enforce로 전환하지 않는다.
- API 응답이
text/html로 나가거나nosniff가 없다.
확인 질문
확인 질문
- SameSite와 CSP의 차이는 무엇인가?
- SameSite는 cookie 전송 정책이고, CSP는 리소스 로딩과 script 실행 정책이다.
- CSP를 report-only로 먼저 적용하는 이유는 무엇인가?
- 실제 서비스 리소스 의존성을 확인하고 오탐 차단을 줄인 뒤 enforce로 전환하기 위해서다.
- 보안 헤더를 응답 계약으로 관리해야 하는 이유는 무엇인가?
- HTML, admin, API, static asset마다 필요한 정책이 다르고 배포 중 누락되면 브라우저 방어선이 약해지기 때문이다.