Content Security Policy (CSP)엄격하게 설정하는 방법
2025. 6. 4. 22:43ㆍ프로그램/PHP 초급
Content Security Policy (CSP)를 엄격하게 설정하는 것은 웹 애플리케이션의 크로스 사이트 스크립팅(XSS) 및 기타 콘텐츠 주입 공격을 방어하는 데 매우 효과적인 방법입니다. 엄격한 CSP는 기본적으로 모든 리소스를 차단하고, 필요한 리소스만 명시적으로 허용하는 "화이트리스트(Whitelist)" 방식을 취합니다.
CSP의 기본 원리
CSP는 웹 페이지가 로드할 수 있는 리소스(스크립트, 스타일시트, 이미지, 폰트, 미디어 등)의 출처(Source)를 웹 서버가 브라우저에게 알려주는 메커니즘입니다. 브라우저는 이 정책에 따라 허용되지 않은 출처의 리소스 로드를 차단합니다.
CSP는 주로 HTTP 응답 헤더를 통해 설정하거나, HTML <meta> 태그를 통해 설정할 수 있습니다. HTTP 헤더를 통해 설정하는 것이 더 강력하고 권장됩니다.
엄격한 CSP 설정을 위한 핵심 원칙
- default-src 'none'; 또는 default-src 'self';로 시작:
- 'none': 가장 엄격한 시작점입니다. 명시적으로 허용하지 않으면 아무것도 로드할 수 없습니다. (권장)
- 'self': 같은 도메인에서 로드되는 리소스만 허용합니다. 이후 다른 모든 지시어(directives)를 개별적으로 명시해야 합니다.
- 'unsafe-inline' 및 'unsafe-eval' 금지:
- script-src나 style-src에서 'unsafe-inline'(인라인 스크립트/스타일 허용)이나 'unsafe-eval'(eval() 함수 허용)을 사용하는 것은 XSS 취약점을 크게 증가시키므로, 엄격한 CSP에서는 절대 사용하지 않아야 합니다.
- 인라인 스크립트/스타일이 필요하다면 nonce (넘버 원스) 또는 hash 방식을 사용해야 합니다.
- nonce (Number Once) 사용 (권장):
- 인라인 스크립트(script-src)나 인라인 스타일(style-src)을 허용하는 가장 안전한 방법입니다.
- 각 HTTP 응답마다 서버에서 **고유하고 예측 불가능한 무작위 문자열(nonce)**을 생성하여 CSP 헤더와 해당 <script> 또는 <style> 태그에 동시에 추가합니다.
- 예: Content-Security-Policy: script-src 'nonce-randomstring'; <script nonce="randomstring">...</script>
- hash 사용:
- 인라인 스크립트/스타일 콘텐츠의 SHA256, SHA384, 또는 SHA512 해시 값을 CSP에 포함시키는 방법입니다.
- 예: Content-Security-Policy: script-src 'sha256-abcdef123456...';
- 정적이고 변경되지 않는 인라인 스크립트에 적합하지만, 동적으로 생성되는 스크립트에는 관리가 어렵습니다.
- object-src 'none';:
- 플래시나 자바 애플릿 같은 레거시 플러그인 콘텐츠를 완전히 차단합니다. 이는 보안 취약점의 일반적인 원인입니다.
- base-uri 'self'; 또는 base-uri 'none';:
- <base> 태그 주입을 방지하여 상대 경로의 기준 URL을 조작하는 공격을 막습니다.
- frame-ancestors 'none';:
- 클릭재킹(Clickjacking) 공격을 방어하는 데 매우 중요합니다. 다른 사이트가 당신의 페이지를 <iframe>, <frame>, <object>, <embed> 등으로 포함하는 것을 완전히 차단합니다. (참고: X-Frame-Options 헤더와 유사하지만, CSP가 더 강력하고 유연합니다.)
- form-action 'self';:
- 폼(form) 데이터가 제출될 수 있는 URL을 제한합니다. 피싱(phishing) 공격을 방지하는 데 도움이 됩니다.
- report-uri 또는 report-to 사용:
- CSP 위반이 발생했을 때 브라우저가 지정된 URL로 위반 보고서를 전송하도록 합니다. 이는 CSP를 테스트하고 배포하는 과정에서 매우 중요합니다.
엄격한 CSP 설정 예시 (HTTP 헤더)
PHP를 사용하여 HTTP 헤더를 설정하는 예시입니다.
PHP
<?php
// 1. 논스(Nonce) 생성
// 매 요청마다 예측 불가능한 고유한 문자열을 생성합니다.
// openssl_random_pseudo_bytes()는 안전한 의사 난수를 생성합니다.
$nonce = base64_encode(openssl_random_pseudo_bytes(16)); // 16바이트 난수 -> Base64 인코딩
// 2. CSP 정책 정의 (매우 엄격한 예시)
$csp_policy = "
default-src 'none';
script-src 'self' 'nonce-" . $nonce . "' https://cdn.example.com;
style-src 'self' 'nonce-" . $nonce . "' https://fonts.googleapis.com;
img-src 'self' data: https://cdn.example.com;
font-src 'self' data: https://fonts.gstatic.com;
connect-src 'self' https://api.example.com wss://realtime.example.com;
media-src 'self';
object-src 'none';
frame-src 'self' https://www.youtube.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
// report-uri는 개발/테스트 단계에서 유용합니다.
// production 환경에서는 report-to를 사용하는 것이 더 최신 방식입니다.
report-uri /csp-report-endpoint;
";
// 불필요한 공백과 줄바꿈 제거
$csp_policy = preg_replace('/\s+/', ' ', trim($csp_policy));
// 3. HTTP 헤더 전송
// Content-Security-Policy-Report-Only: 정책을 위반해도 차단하지 않고 보고만 합니다. (테스트/디버깅용)
// Content-Security-Policy: 정책을 위반하면 차단하고 보고합니다. (운영용)
header("Content-Security-Policy: " . $csp_policy);
// header("Content-Security-Policy-Report-Only: " . $csp_policy); // 테스트 시 사용
// 4. HTML 콘텐츠 출력 (nonce 적용 예시)
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>엄격한 CSP 적용 예시</title>
<style nonce="<?= $nonce ?>">
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<h1>엄격한 CSP 테스트 페이지</h1>
<script nonce="<?= $nonce ?>">
// 이 스크립트는 nonce가 일치하므로 실행됩니다.
console.log('이 스크립트는 CSP에 의해 허용되었습니다.');
alert('CSP 적용 성공!');
// CSP에 의해 차단될 외부 스크립트 (example.com은 허용했지만, non-allowed-domain.com은 허용하지 않음)
// var script = document.createElement('script');
// script.src = 'https://non-allowed-domain.com/malicious.js';
// document.body.appendChild(script);
// CSP에 의해 차단될 인라인 이벤트 핸들러 (엄격한 CSP에서 인라인 이벤트는 허용되지 않음)
// document.getElementById('myButton').onclick = function() { alert('Clicked!'); };
</script>
<button id="myButton">클릭</button>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script>
// jQuery는 CDN에서 로드되므로, script-src에 해당 CDN이 포함되어야 합니다.
$(document).ready(function() {
$('#myButton').click(function() {
console.log('버튼 클릭됨!');
});
});
</script>
</body>
</html>
엄격한 CSP 설정 시 고려사항
- 점진적 적용 (Report-Only 모드):
- CSP를 처음 적용할 때는 Content-Security-Policy-Report-Only 헤더를 사용하여 시작하는 것이 매우 중요합니다.
- 이 모드에서는 정책을 위반해도 브라우저가 콘텐츠를 차단하지 않고, 단지 report-uri (또는 report-to)로 보고서만 전송합니다.
- 이를 통해 애플리케이션의 모든 리소스 로드 경로를 파악하고, 실제 차단 없이 위반 사항을 모니터링하여 정책을 완벽하게 다듬을 수 있습니다.
- 모든 리소스 파악:
- JS 라이브러리 (CDN 포함), CSS 파일, 이미지, 웹 폰트, AJAX 요청 엔드포인트, WebSocket 연결, iframe 콘텐츠, 폼 전송 대상 등 모든 리소스의 출처를 정확히 파악해야 합니다.
- 특히 타사 위젯(소셜 미디어 버튼, 광고, 분석 스크립트)은 종종 복잡한 로드 방식을 사용하므로 주의 깊게 살펴봐야 합니다.
- 인라인 스크립트/스타일 제거 또는 Nonce/Hash 적용:
- 가장 큰 도전 과제입니다. 모든 onclick, onmouseover 등의 인라인 이벤트 핸들러를 제거하고 JavaScript 이벤트 리스너로 변경해야 합니다.
- <script> 태그 내의 모든 인라인 JavaScript 코드와 <style> 태그 내의 모든 인라인 CSS 코드에도 nonce 또는 hash를 적용해야 합니다. Nonce는 동적 콘텐츠에, Hash는 정적 콘텐츠에 유리합니다.
- eval() 및 setTimeout(string), new Function() 사용 금지:
- 이러한 함수는 문자열을 코드로 실행하므로 XSS에 매우 취약합니다. 엄격한 CSP는 이들의 사용을 unsafe-eval 없이 차단합니다. 필요한 경우 다른 방식으로 코드를 작성해야 합니다.
- 개발자 도구 활용:
- 브라우저의 개발자 도구(Console, Network 탭)는 CSP 위반을 감지하고 디버깅하는 데 필수적입니다. 위반 사항이 콘솔에 자세히 기록됩니다.
엄격한 CSP를 설정하는 것은 복잡할 수 있지만, 웹 애플리케이션의 보안을 크게 강화하는 데 필수적인 단계입니다. 점진적인 접근 방식과 철저한 테스트를 통해 성공적으로 구현할 수 있습니다.
'프로그램 > PHP 초급' 카테고리의 다른 글
| macOS에서 파일 업로드 시 발생하는 한글 자소 분리 문제를 해결 (0) | 2025.06.04 |
|---|---|
| PHP 함수가 있는지 체크하고 정의 하는 방법 (0) | 2025.06.04 |
| PHP Path Combine 방법 (0) | 2025.06.04 |
| PHP SELF 와 query string 결합하는 방법 (0) | 2025.06.04 |
| PHP 텍스트에서 HTML 제거 하는 방법 (0) | 2025.06.04 |