CSP 'upgrade-insecure-requests' 이슈
2024. 7. 5. 10:26

CSP(Content Security Policy) 란

csp는 웹 보안 표준으로, 웹사이트에서 악의적인 콘텐츠가 실행되는 것을 방지하기 위한 강력한 도구다. CSP는 웹 애플리케이션이 브라우저에 어떤 리소스를 로드하고 실행할 수 있는지 명시적으로 정의하는 데 사용된다.

  • XSS 방지: 악성 스크립트가 웹 페이지에서 실행되는 것을 방지.
  • 데이터 삽입 공격 방지: 정적 콘텐츠와 동적 콘텐츠를 명확히 구분하여 공격을 방지.
  • 보안 정책 적용: 리소스 로드 및 실행에 대한 규칙을 명확히 설정.

참고로 next js의 경우 next.config.mjs의 헤더부분에서 csp 설정을 해주면 된다.

https://nextjs.org/docs/pages/api-reference/next-config-js/headers

 

const nextConfig = {
	...,
	async headers() {
    	return [
            {
               source: "/:path*",
               headers: [
                 {
                   key: "Content-Security-Policy",
                   value: "..."
                 }
               ]
            },
    	]
    }
}



upgrade-insecure-requests 란

리소스를 요청해서 받아올 때 https가 아닌 http로 받아 올 경우 보안 및 개인정보 보호에 문제가 있을 수 있다.

그래서 csp에 upgrade-insecure-requests를 설정할 경우 브라우저가 이를 인식하고 http에 대한 리소스 요청을 https로 자동으로 변경한다.

 

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests

 

 

개발 환경에서 문제점

next js로 개발 중 upgrade-insecure-requests로 설정하고 크롬의 localhost(개발 환경)에서는 별 문제가 없었는데 사파리로 localhost에 접속하니 오류가 발생했다.

 

문제 원인은 개발환경은 http인데 csp설정 때문에 https로 리소스를 요청하니 응답을 받아오지 못하는 것이었다.

 

 

하지만 크롬 같은 경우는 확인해 보니 upgrade-insecure-requests설정을 하더라도 https로 리다이렉트 하지 않고 http로 그대로 받아오고 있었기 때문에 정상작동하는 것을 확인했다.

 

왜 이렇게 작동하는지 참고문헌을 찾지는 못했지만 필자의 생각으로는...

Production 환경에서 https가 아닌 http를 사용하는 케이스는 거의 없기 때문에

http에 접속해 리소스를 요청한다는 것은 보안이 중요하지 않거나 개발환경인 케이스이기 때문에 크롬은 upgrade-insecure-requests설정을 무시하고 https로 리다이렉트를 하지 않는 것으로 보인다.

 

크롬의 정책, 사파리의 정책 중 어느 것이 옳으냐는 관점의 문제인 것 같기는 한데 개인적으로는 크롬의 정책이 더 편리하고 맞지 않나 싶다...

 

 

문제 해결

사파라의 경우도 원활하게 사이트접속이 가능하도록 production 환경인지 구분하여 csp 설정을 적용하였다.

(Production 환경이 아닌 경우 upgrade-insecure-requests 미적용)

 

const getCSP = () => {
  // 배포, 개발환경 구분자
  const isProduction = process.env.NODE_ENV === "production";

  const ContentSecurityPolicy = `default-src 'self';
  ${isProduction ? "upgrade-insecure-requests;" : ""}
  ...
`;

  return ContentSecurityPolicy;
};


const nextConfig = {
    async headers() {
      return [
        {
            source: "/:path*",
            headers: [
              {
                key: "Content-Security-Policy",
                value: getCSP().replace(/\n/g, "")
              }
            ]
        },
      ]
    }
}