-
CSP(Content Security Policy)를 적용해보자개발 지식/웹 기초·보안 2026. 10. 5. 13:21
최근 개발하고 있는 웹 사이트에 보안을 조금 더 강화해야 하는 일이 생겼습니다.
일부 사이트에서는 더 높은 수준의 보안을 요구하고 있었고, 웹 사이트에 보안 취약점이 있는지 확인하기 위해 ZAP(Zed Attack Proxy)을 사용하여 검사를 진행하였습니다.
그런데 검사 결과에서 아래와 같은 항목이 발견되었습니다.
CSP Header Not SetCSP Header가 설정되어 있지 않다는 의미였습니다.
CSP라는 단어는 들어본 적이 있었지만 실제로 적용해 본 적은 없었기 때문에, 이번 기회에 CSP가 무엇인지 알아보고 직접 적용해 보았습니다.
CSP (Content Security Policy)
CSP는 Content Security Policy의 약자로 콘텐츠 보안 정책을 의미합니다.
웹 페이지에서 스크립트, 스타일과 같은 리소스를 어디에서 불러오거나 실행할 수 있는지 제한할 수 있는 보안 정책입니다.
만약 외부에서 삽입된 악성 스크립트가 아무런 제한 없이 실행될 수 있다면 XSS와 같은 공격이 발생할 수 있습니다.
CSP를 사용하면 브라우저에서 허용할 리소스의 출처나 실행 방식을 제한하여 이러한 공격을 방지하는 데 도움을 줄 수 있습니다.
그렇다면 CSP는 어떻게 적용할 수 있을까요?
CSP 적용
CSP를 적용하는 방법을 찾아보니 HTML의 <meta> 태그를 통해 설정하는 방법이 있었습니다.
그래서 처음에는 아래와 같이 CSP를 추가하였습니다.
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />http-equiv에 Content-Security-Policy를 지정하고 content에 CSP 정책을 작성하면 됩니다.
처음 CSP를 설정하면서 가능하면 현재 사이트에서 제공하는 리소스만 사용할 수 있도록 'self'를 기준으로 정책을 설정하였습니다.
실제로 적용하려고 했던 정책은 아래와 같습니다.
default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; frame-ancestors 'self';그럼 각각 어떤 역할을 하는지 알아보겠습니다.
default-src
default-src 'self';별도의 Fetch Directive가 지정되지 않았을 때 사용되는 기본 정책입니다.
'self'를 지정하면 현재 페이지와 동일한 출처의 리소스를 허용합니다.
처음에는 대부분의 리소스를 현재 사이트에서 가져오도록 제한하기 위해 'self'를 사용하였습니다.
script-src
script-src 'self' 'wasm-unsafe-eval';JavaScript와 같은 스크립트의 출처와 실행에 대한 정책을 설정합니다.
'self'를 사용하면 동일한 출처에서 제공되는 스크립트를 허용합니다.
CSP를 적용하면서 unsafe-eval은 사용하지 않았습니다.
unsafe-eval을 사용하면 문자열을 JavaScript 코드로 실행하는 eval()과 같은 동작을 허용하게 되는데, 보안상 위험할 수 있으며 ZAP에서도 안전하지 않은 CSP 설정으로 판단할 수 있습니다.
그런데 현재 개발하고 있는 사이트에서는 WebAssembly를 사용하고 있었습니다.
WebAssembly를 사용하기 위해 unsafe-eval을 추가해야 하나 싶었지만, 알아보니 WebAssembly 실행만 별도로 허용할 수 있는 'wasm-unsafe-eval'이 존재하였습니다.
따라서 아래와 같이 설정하였습니다.
script-src 'self' 'wasm-unsafe-eval';이를 통해 unsafe-eval을 허용하지 않으면서 필요한 WebAssembly 실행은 허용할 수 있었습니다.
style-src
style-src 'self';스타일을 불러올 수 있는 출처와 적용 방식을 제한합니다.
여기서도 처음에는 문제가 하나 있었습니다.
기존 코드에서 inline style을 사용하고 있었기 때문입니다.
CSP에서 아래와 같이 설정하면 inline style은 허용되지 않습니다.
style-src 'self';간단하게 해결하려면 'unsafe-inline'을 추가하는 방법도 있습니다.
style-src 'self' 'unsafe-inline';하지만 unsafe-inline을 허용하면 CSP를 적용하는 의미가 약해지고 ZAP에서도 보안상 문제가 있는 설정으로 판단할 수 있었습니다.
결국 unsafe-inline을 추가하는 대신 기존에 사용하고 있던 inline style을 수정하였습니다.
그리고 최종적으로는 아래와 같이 'self'만 허용하도록 설정하였습니다.
style-src 'self';처음에는 CSP Header만 추가하면 될 것이라고 생각했는데, 실제로 적용해 보니 CSP 정책에 맞게 기존 코드도 함께 수정해야 했습니다.
frame-ancestors
frame-ancestors 'self';현재 페이지를 <iframe>과 같은 형태로 포함할 수 있는 출처를 지정합니다.
'self'를 지정하면 동일한 출처의 페이지에서만 현재 페이지를 포함할 수 있습니다.
이렇게 CSP를 적용하고 다시 ZAP 검사를 진행하였습니다.
새로운 문제가 생겼다
처음에는 CSP를 추가하였으니 문제가 해결되었을 것이라고 생각했습니다.
하지만 다시 ZAP 검사를 해보니 새로운 항목이 발견되었습니다.
CSP: Meta Policy Invalid DirectiveMeta로 설정한 CSP에 사용할 수 없는 Directive가 존재한다는 의미였습니다.
처음에는 CSP를 잘못 작성한 것이라고 생각하여 하나씩 확인해 보았습니다.
그리고 원인은 frame-ancestors였습니다.
그렇다면 frame-ancestors에는 어떤 문제가 있었을까요?
frame-ancestors는 Meta에서 사용할 수 없다
frame-ancestors도 CSP에서 사용하는 Directive이기 때문에 당연히 <meta> 태그에서도 사용할 수 있을 것이라고 생각했습니다.
하지만 알아보니 frame-ancestors는 <meta> 태그를 통해 설정할 수 없는 Directive였습니다.
따라서 아래와 같이 작성해도 frame-ancestors는 정상적으로 적용되지 않습니다.
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; frame-ancestors 'self';" />frame-ancestors를 사용하기 위해서는 CSP를 HTML의 <meta> 태그가 아닌 HTTP Response Header를 통해 전달해야 합니다.
결국 처음 적용했던 방법을 변경하기로 하였습니다.
Response Header로 변경
기존 <meta> 태그의 CSP 설정을 제거하고 서버에서 HTTP Response Header에 CSP를 추가하도록 변경하였습니다.
Content-Security-Policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; frame-ancestors 'self';이제 브라우저가 서버의 응답을 받을 때 Content-Security-Policy Header를 함께 전달받게 됩니다.
그리고 기존 <meta> 방식에서는 사용할 수 없었던 frame-ancestors도 정상적으로 적용할 수 있게 되었습니다.
처음에는 CSP를 적용하기만 하면 해결되는 간단한 문제라고 생각했지만, 실제로 적용해 보니 CSP의 Directive에 따라 적용할 수 있는 방법이 다르고 기존 코드도 정책에 맞게 수정해야 한다는 것을 알게 되었습니다.
후기
처음에는 ZAP에서 발견된 CSP Header Not Set을 해결하기 위해 CSP에 대해 알아보기 시작하였습니다.
CSP를 알아보고 <meta> 태그를 사용하여 적용하면 간단하게 해결될 것이라고 생각했지만, 실제로 적용하는 과정에서 생각보다 여러 가지 문제를 만났습니다.
기존 코드에서는 inline style을 사용하고 있었지만 ZAP 검사를 통과하기 위해 unsafe-inline을 허용하는 대신 기존 코드를 수정해야 했습니다.
JavaScript에서도 unsafe-eval을 사용할 수 없었지만 WebAssembly가 필요했기 때문에 wasm-unsafe-eval을 사용하였습니다.
그리고 다시 ZAP 검사를 진행하니 CSP: Meta Policy Invalid Directive라는 새로운 문제가 발생하였습니다.
처음에는 CSP 설정이 잘못된 줄 알았지만 원인을 찾아보니 frame-ancestors는 <meta> 태그에서 사용할 수 없고 Response Header를 통해 설정해야 한다는 것도 알게 되었습니다.
단순히 ZAP에서 발견된 취약점을 해결하기 위해 시작했지만, CSP를 적용하면서 단순히 Header 하나를 추가하는 것이 아니라 기존 코드도 CSP 정책에 맞게 수정해야 한다는 것을 알 수 있었습니다.
보안과 관련된 설정은 경고를 없애기 위해 허용 범위를 넓히는 것보다 필요한 기능만 허용하도록 기존 코드를 수정하는 것이 더 중요하다는 생각이 들었습니다.
앞으로도 ZAP에서 발견되는 항목들을 하나씩 확인하면서 어떤 취약점이고, 왜 발생하는지 알아가면서 적용해 봐야겠습니다.
참고
MDN - Content Security Policy (CSP)
'개발 지식 > 웹 기초·보안' 카테고리의 다른 글
웹 접근성과 웹 표준을 알아보자! (0) 2024.05.22 기억해줘! 기억할게! (쿠키, 세션, 토큰) (0) 2023.11.28