Skip to main content

[Security] Content-Security-Policy

Content-Security-Policy header is not set. It restricts resources the page can load and prevents XSS attacks.

seo 진단시 위와 같은 진단내용이 잡힌다면 아래 내용들을 확인해주세요.

Content-Security-Policy(CSP)란?

CSP는 HTTP 응답 헤더 중 하나로, 브라우저가 페이지에 어떤 리소스를 로드할 수 있는지 제어하는 보안 정책을 정의합니다.
이를 통해 아래와 같은 보안 위협을 방지할 수 있습니다:

  1. XSS(크로스 사이트 스크립팅)
    공격자가 악성 JavaScript를 주입하는 것을 방지합니다.
  2. 데이터 주입 공격
    악성 리소스(URL, CSS, 폰트 등)의 로드를 제한합니다.
  3. Clickjacking
    악성 iframe 삽입 등을 방지합니다.

Content-Security-Policy가 중요한 이유

CSP가 없으면 다음과 같은 문제가 발생할 수 있습니다:

  • 외부에서 악성 스크립트를 주입하여 사용자 세션 탈취, 피싱 공격 가능.
  • 비인가된 리소스가 로드되어 정보 유출 가능.
  • HTTPS 환경에서도 보안이 취약한 HTTP 리소스가 로드될 가능성.

해결 방법: Content-Security-Policy 헤더 설정

Apache 설정을 통해 CSP 추가


Apache에서 Content-Security-Policy 헤더를 설정하려면 .htaccess 파일이나 서버 설정 파일(httpd.conf, apache2.conf)에 다음 규칙을 추가합니다.

<IfModule mod_headers.c>
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com https://d3js.org https://cdnjs.cloudflare.com https://cdn.jsdelivr.net 'unsafe-inline' 'unsafe-eval'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net data:; connect-src 'self' https://www.google-analytics.com; img-src 'self' data: https://cdn.jsdelivr.net;"
</IfModule>

default-src 'self'

현재 사이트 외의 다른 출처에서 리소스(스크립트, 스타일, 이미지)를 불러오지 못하게 방지합니다.

self

현재 사이트 자체에서 제공되는 스크립트만 허용합니다.

unsafe-inline

인라인 스크립트를 허용합니다 (보안상 추천되지 않음).

unsafe-eval

eval()이나 setTimeout(string) 같은 동적 코드 실행을 허용합니다.

data:

데이터 URI를 통해 폰트, 이미지 등의 데이터를 허용합니다.

 

 

* 줄바꿈이 포함되면 500 server error가 발생할 수 있습니다.

 

2. 프론트에서 미들웨어 추가 (라라벨 기준)

1. app/Http/Middleware에 새로운 class를 생성합니다.

2. 아래와 같이 handle function을 추가합니다. $csp 변수 안에 내용은 직접 변경하셔야합니다. 

public function handle(Request $request, Closure $next)
    {
        $response = $next($request);


        $csp = "default-src 'self'; " .
       "script-src 'self' https://www.googletagmanager.com https://d3js.org https://cdnjs.cloudflare.com https://cdn.jsdelivr.net 'unsafe-inline' 'unsafe-eval'; " .
       "style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; " .
       "font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net data:; " .
       "connect-src 'self' https://www.google-analytics.com; " .
       "img-src 'self' data: https://cdn.jsdelivr.net;";


        $response->headers->set('Content-Security-Policy', $csp);


        return $response;
    }

3. app/Http/Kernel.php에 미들웨어를 추가합니다.

protected $middleware = [ \App\Http\Middleware\ContentSecurityPolicy::class, ];

권장사항

엄격한 정책으로 전환

  • 초기에는 unsafe-inline을 사용할 수 있지만, 최종 배포시 제거하는 것이 좋습니다.

  • nonce 또는 hash 기반 CSP를 사용하여 특정 스크립트만 허용하도록 설정하는 것이 좋습니다.

Nonce를 사용 (권장)


아래 코드는 nonce 적용에 대한 예시코드입니다.

- apache 설정

Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com https://d3js.org https://cdnjs.cloudflare.com 'nonce-abc123'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; img-src 'self' data:;"

- script 사용부분

<script nonce="abc123"> console.log('Inline script with nonce'); </script>

- 동적 Nonce 생성

<?php 
// php + javascript
$nonce = base64_encode(random_bytes(16)); 
header("Content-Security-Policy: script-src 'self' 'nonce-$nonce'"); 
?> 

<script nonce="<?php echo $nonce; ?>"> 
  console.log('This script uses a nonce'); 
</script>
<script>
  // only javascript
  // Generate a random nonce
  const array = new Uint8Array(16); 
  window.crypto.getRandomValues(array); 
  const nonce = btoa(String.fromCharCode.apply(null, array)); 

  // Set the Content-Security-Policy header
  const meta = document.createElement('meta'); 
  meta.setAttribute('http-equiv', 'Content-Security-Policy'); 
  meta.setAttribute('content', `script-src 'self' 'nonce-${nonce}'`); 
  document.head.appendChild(meta); 

  // Dynamically create a script element with the nonce
  const script = document.createElement('script'); 
  script.setAttribute('nonce', nonce); 
  script.textContent = "console.log('This script uses a nonce')"; 
  document.body.appendChild(script); 
</script>