[Django 6.0] Content Security Policy: XSS 방어의 최종형
Django CSP, Content Security Policy, django csp middleware, csp-report-only, CSP nonce
정의
Content Security Policy (CSP) = 브라우저에게 어떤 스크립트/스타일/이미지 만 허용 할지 선언. XSS 공격의 마지막 방어선.
Django 6.0 부터 CSP 지원이 내장 (옛 django-csp 서드파티 → Django 표준). 자세한 XSS 는 django-security 참고.
목적 + 대안
| 접근 | 의미 |
|---|---|
| 입력 필터링 | 사용자 입력에서 <script> 제거 (완벽하지 않음) |
| 출력 escape | {% autoescape %} 로 HTML escape (Django 기본) |
| CSP | 브라우저에게 인라인 스크립트 실행 금지 지시 |
IMPORTANT
CSP 는 다중 방어 의 하나. Escape + Sanitize + CSP 3중 방어가 정통.
CSP Header 구조
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
report-uri /csp-report/
| Directive | 의미 |
|---|---|
default-src | 기본값 |
script-src | JavaScript 허용 원본 |
style-src | CSS |
img-src | 이미지 |
connect-src | fetch, XHR, WebSocket |
font-src | 폰트 |
frame-src / frame-ancestors | iframe |
form-action | form action target |
report-uri | 위반 리포트 |
Django 6.0 CSP 설정
# settings.py
MIDDLEWARE = [
...
'django.middleware.csp.ContentSecurityPolicyMiddleware',
]
SECURE_CSP = {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://cdn.example.com'],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'connect-src': ["'self'", 'https://api.example.com'],
'frame-ancestors': ["'none'"],
'report-uri': ['/csp-report/'],
}
Report-Only 모드 (안전한 배포)
# 처음 도입 시 report-only 로 시작
SECURE_CSP_REPORT_ONLY = {
'default-src': ["'self'"],
'script-src': ["'self'"],
'report-uri': ['/csp-report/'],
}
브라우저가 위반 감지 시 report 만 전송, 실제 차단 안 함. 로그 분석 후 정식 정책으로 전환.
Nonce 로 인라인 스크립트 허용
# views.py
from django.middleware.csp import get_csp_nonce
def home(request):
return render(request, 'home.html', {
'csp_nonce': get_csp_nonce(request),
})
<script nonce="{{ csp_nonce }}">
console.log('inline script allowed');
</script>
Header:
Content-Security-Policy: script-src 'self' 'nonce-xyz123abc';
TIP
동적 인라인 스크립트 를 안전하게. 'unsafe-inline' 보다 훨씬 안전.
Hash (정적 인라인)
SECURE_CSP = {
'script-src': [
"'self'",
"'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8='",
],
}
특정 인라인 스크립트 hash 계산 후 허용. Nonce 와 달리 정적 스크립트만.
위반 리포트 처리
# views.py
import json
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def csp_report(request):
if request.method == 'POST':
report = json.loads(request.body)
logger.warning('CSP violation: %s', report)
return HttpResponse(status=204)
{
"csp-report": {
"document-uri": "https://example.com/page/",
"referrer": "",
"violated-directive": "script-src 'self'",
"blocked-uri": "https://evil.com/xss.js",
"line-number": 42
}
}
Strict CSP (현대 권장)
Google 이 권장하는 strict CSP:
SECURE_CSP = {
'script-src': ["'strict-dynamic'", "'nonce-{{nonce}}'"],
'object-src': ["'none'"],
'base-uri': ["'none'"],
}
strict-dynamic: nonce 통과한 스크립트가 로드한 자원은 자동 허용.object-src 'none': Flash/plugin 완전 차단.
흔한 함정
WARNING
unsafe-inline남발 = CSP 의 의미 없음. Nonce 또는 hash 사용.- CDN URL 하드코딩 = CDN 변경 시 앱 다운. 설정 관리.
- report-uri 없이 시작 = 위반 파악 못함. 반드시 report 먼저.
'unsafe-eval'필요 = React 개발 mode 등. Production 은 build 후 제거.
다른 프레임워크
| Framework | CSP 지원 |
|---|---|
| Django 6.0+ | 내장 |
| Django 5.x 이하 | django-csp 서드파티 |
| Rails | Rails 5.2+ 내장 (config.content_security_policy) |
| Spring | Spring Security header |
| Express | helmet.js |
| Next.js | next.config.js headers |
관련 위키
이 글의 용어 (4개)
- [Auth] CSRF: 위조 요청 공격과 방어auth-security
- 정의 CSRF (Cross-Site Request Forgery) 는 사용자가 로그인된 상태 에서 공격자가 만든 페이지 가 피해자 브라우저로 위조 요청 을 보내는 공격. 쿠키 기…
- [Django] 보안: CSRF, XSS, SQL injection, headersdjango
- 정의 Django는 OWASP Top 10 대부분을 기본 방어한다: CSRF 토큰, ORM의 SQL 인젝션 방지, 템플릿 자동 escape, 비밀번호 해싱, 세션 쿠키 보안 등.…
- [Django] Middleware: 요청/응답 가로채기django
- 정의 Middleware는 모든 요청/응답을 가로채는 체인 구조 처리기. 요청은 위 → 아래, 응답은 아래 → 위로 흐른다. 인증, 세션, CSRF, gzip 압축, 보안 헤더 …
- [Network] CORS: Same-Origin Policy, preflight, credentialsnetwork
- 정의 CORS (Cross-Origin Resource Sharing) 는 Same-Origin Policy (SOP) 의 완화 메커니즘. 브라우저가 origin 이 다른 리소스…
💬 댓글