CSP를 켰는데 unsafe-inline을 넣는 순간 의미가 사라진 이유
문제 발생
XSS 방어를 강화하려고 CSP를 넣었더니 화면이 깨졌습니다. 인라인 스크립트와 스타일이 전부 막혔기 때문입니다. 검색해서 나온 대로 이렇게 고쳤습니다.
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'화면은 정상으로 돌아왔지만, 이 헤더가 막아주는 것은 사실상 없었습니다.
원인 분석
'unsafe-inline'이 정확히 그 보호를 끄는 스위치입니다. MDN이 경고로 적습니다 — 개발자는 'unsafe-inline'을 피해야 하며, 이것은 CSP를 두는 목적의 상당 부분을 무력화합니다.
기본 동작을 보면 이유가 분명합니다. CSP에 default-src나 script-src가 있으면 인라인 <script> 태그, 인라인 이벤트 핸들러 속성, javascript: URL이 실행되지 않습니다. XSS의 대표적인 형태가 전부 이 셋입니다. 'unsafe-inline'은 이 보호를 통째로 되돌립니다.
대신 쓰라는 것이 nonce입니다. MDN 설명대로 서버가 모든 HTTP 응답마다 이 무작위 값을 생성하고, 문서에서 로드할 <script>/<style>의 nonce 속성에 같은 값을 넣습니다. 브라우저는 CSP 지시문의 값과 요소 속성의 값을 비교해 일치할 때만 자원을 로드합니다.
두 조건이 핵심입니다 — 응답마다 재생성되어야 하고 추측 불가능해야 합니다. 정적 문자열로 박아두면 공격자가 그 값을 자기 스크립트에 붙이면 그만입니다.
해결 방안
- 응답마다 nonce를 만들어 헤더와 태그에 함께 넣습니다.
Content-Security-Policy: script-src 'nonce-{랜덤값}' 'strict-dynamic'; object-src 'none'; base-uri 'none'<script nonce="{같은 값}">…</script>-
동적으로 스크립트를 넣는 앱에는
'strict-dynamic'을 씁니다. MDN 설명대로 nonce나 해시로 부여된 신뢰를 그 스크립트가 동적으로 로드하는 스크립트까지 확장합니다. 이 키워드가 있으면 호스트 목록, 스킴,'self','unsafe-inline'은 무시됩니다. -
먼저 리포트 전용으로 돌립니다.
Content-Security-Policy-Report-Only는 위반을 보고하되 차단하지 않습니다. 실제 트래픽에서 무엇이 깨지는지 본 다음 강제로 전환합니다. -
'unsafe-eval'도 같은 성격입니다. 라이브러리가 요구한다면 그 라이브러리를 바꾸는 쪽을 먼저 검토합니다. -
인라인 스타일과 스크립트를 줄이는 게 근본입니다. 프레임워크가 만들어내는 인라인 조각이 문제라면 그 프레임워크의 nonce 지원(예: Next.js의 proxy에서 nonce 주입)을 씁니다.
-
CSP는 XSS 방어의 마지막 층입니다. 입력 검증, 출력 이스케이프,
dangerouslySetInnerHTML금지를 대체하지 않습니다 — 그 층들이 뚫렸을 때 피해를 줄이는 장치입니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.