본문 바로가기

회고와 이슈/이슈와 정보

여러분의 사이트는 안전한가요? ① | CSP 와 X-frame-Options

반응형

들어가는 말

이전 포스트에서 제가 만들었던 명언 웹 사이트( https://wise-sayings.com/ ) 의 웹 보안 수준을 MDN 에서 제공하는 무료 도구를 사용해서 측정했었고, 30/100 으로 낮은 점수를 받았습니다. 그중 -20이나 차지하는 부분이 CSP  였는데, CSP 가 무엇이고, 이를 사이트에 어떻게 적용시키는지 그 과정을 정리해볼까 합니다. 

참고로 제가 만든 웹 사이트는 NextJS 14 버전으로 개발되었습니다.

 

 

CSP(컨텐츠 보안 정책)과 X-frame-Options

CSP

CSP 는 말그대로 컨텐츠의 안전을 책임지는 정책 입니다. 교차 사이트 스크립팅(XSS)과 데이터 주입 공격을 비롯한 특정 유형의 공격을 탐지하고 완화하는 데 도움이 되는 추가 보안 계층으로 이러한 공격은 데이터 절도에서 사이트 훼손, 맬웨어 배포에 이르기까지 모든 것에 사용 됩니다(https://developer.mozilla.org/ko/docs/Web/HTTP/CSP).

 

위키 백과에서는 CSP 를 다음과 같이 정리해두었습니다. 

콘텐츠 보안 정책 (Content Security Policy, CSP)은 신뢰된 웹 페이지  콘텍스트에서 악의적인 콘텐츠를 실행하게 하는 사이트 간 스크립팅 (XSS),  클릭재킹 , 그리고 기타 코드 인젝션  공격을 예방하기 위해 도입된  컴퓨터 보안
표준이다. 웹 애플리케이션 보안의 W3C  워킹 그룹의 후보 권고안이며  현대의 웹 브라우저 에 폭넓게 지원된다.

CSP는 웹사이트 소유자들이 승인된 콘텐츠 오리진(origin)을 선언할 수 있게 하는 표준 방식을 제공하며, 이를 통해 해당 웹사이트들로부터 브라우저들이 자바스크립트, CSS, 프레임, 웹 워커,  글꼴, 이미지, 그리고 자바 애플릿, 액티브X, 오디오 및 비디오 파일, 그리고 기타 HTML5 기능들을 사용할 수 있게 허용이 가능해진다.

https://ko.wikipedia.org/wiki/%EC%BD%98%ED%85%90%EC%B8%A0_%EB%B3%B4%EC%95%88_%EC%A0%95%EC%B1%85

 

X-Frame-Options 와 CSP:frame-ancestors

X-Frame-Options 와 CSP:frame-ancestors 는 흔히 타 사이트를 자신의 사이트 내에서 팝업 혹은 프레임 형태로 불어와서 사용하는 형식에 대한 보안 설정을 나타냅니다. 예를 들어 <frame>,<iframe>,<object>,<embed> 와 같은 태그를 사용하여 표현합니다. 즉, A 라는 사이트의 콘텐츠를 B라는 사이트에서 팝업 형태로 불러와서 마치 B 사이트 내의 콘텐츠 중 하나인 것처럼 사용할 수 있게 됩니다.

 

단, 이는 XSS 와 관련해서 보안상 매우 좋지 못한 방식이므로 정말 보안상 안전하다고 보장할 수 있는 상황이 아니면 피해야하는 방식입니다. 이러한 점에서 X-Frame-Options 와 frame-ancestros 는 현재 여러분이 만든 사이트를 다른 사이트에서 쓸 수 있도록 하는 것을 막을 수 있도록 해줍니다. 

 

이 옵션을 적용하게 된다면, 클릭재킹 문제방지할 수 있습니다.

클릭재킹이란 웹사이트 사용자를 속여 자신도 모르게 악성 링크를 클릭하도록 하는 인터페이스 기반 공격입니다. 클릭재킹을 통해, 공격자는 악성 링크를 웹사이트의 버튼이나 합법적인 페이지에 삽입합니다. 감염된 사이트에서 사용자가 합법적인 링크를 클릭할 때마다, 공격자는 해당 사용자의 기밀 정보를 얻게 되고, 궁극적으로 인터넷에서 사용자의 개인 정보를 손상시킵니다.

 

 frame-ancestors 는 X-Frame-Options 가 레거시되면서 사용가능해진 CSP 옵션으로 대체버전 입니다. 이제는 X-Frame-Options 이 HTTP 헤더에 적용되지 않는다고 하는데, 이 부분은 아래 MDN 문서를 참고해보시길 바랍니다.

 

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options

 

X-Frame-Options - HTTP | MDN

The X-Frame-Options HTTP response header can be used to indicate whether a browser should be allowed to render a page in a <frame>, <iframe>, <embed> or <object>. Sites can use this to avoid click-jacking attacks, by ensuring that their content is not embe

developer.mozilla.org

 

 

CSP 의 활용

CSP 를 적용하면 어떤 이점을 얻을 수 있을까요? MDN 에서는 다음 두 가지로 정리하여 설명하고 있습니다.

 

교차 사이트 스크립팅 완화

CSP의 주요 목표는 XSS 공격을 완화하고 보고하는 것입니다. XSS 공격은 서버에서 받은 콘텐츠를 브라우저가 신뢰한다는 점을 악용합니다. 브라우저는 콘텐츠의 출처를 신뢰하기 때문에 콘텐츠가 이상한 곳에서 오더라도 악성 스크립트를 피해자의 브라우저에서 실행합니다.

CSP를 사용하면 서버 관리자가 브라우저에서 실행 가능한 스크립트의 유효한 소스로 간주해야 하는 도메인을 지정하여 XSS가 발생할 수 있는 벡터를 줄이거나 제거할 수 있습니다. 그러면 CSP 호환 브라우저는 허용된 도메인에서 받은 소스 파일에서 로드된 스크립트만 실행하고 HTML 속성을 포함한 인라인 스크립트 및 이벤트 처리 등의 다른 모든 스크립트는 무시합니다.

궁극적인 보호 형태로서 스크립트 실행을 허용하지 않으려는 사이트는 전역적으로 스크립트 실행을 허용하지 않도록 선택할 수 있습니다.

 

패킷 스니핑 공격 완화

콘텐츠를 로드할 수 있는 도메인을 제한하는 것 외에도 서버는 사용할 수 있는 프로토콜을 지정할 수 있습니다. 예를 들어(이상적으로는 보안 관점에서) 서버는 모든 콘텐츠가 HTTPS를 사용하여 로드되도록 지정할 수 있습니다. 완전한 데이터 전송 보안 전략에서는 데이터 전송을 위해 HTTPS를 적용할 뿐만 아니라 모든 쿠키에 secure 속성을 표시하고 HTTP 페이지는 해당 HTTPS 페이지로 자동 리디렉션을 제공합니다. 사이트는 Strict-Transport-Security HTTP 헤더를 사용하여 브라우저가 암호화된 채널을 통해서만 사이트에 연결하도록 할 수도 있습니다.

 

[실습] NextJS13~ 에서 CSP 적용해보기

앞서 CSP 개념에 대해 일부 살펴보았고, 이제 사이트에 직접 적용해보는 시간을 가져볼까 합니다. 우선, CSP 는 HTTP 헤더에 설정해둡니다. NextJS 13 버전 이상을 기준으로 next.config.js 파일에서 header 옵션을 사용하여 추가할 수 있습니다.

 

https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy

 

Configuring: Content Security Policy | Next.js

Learn how to set a Content Security Policy (CSP) for your Next.js application.

nextjs.org

 

 

아래는 next.config.js 파일인데, 예시를 위한 부분만 제외하고 간소화된 형태로 적혀 있습니다. 

/** @type {import('next').NextConfig} */

const ContentSecurityPolicy = `
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
`

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: ContentSecurityPolicy.replace(/\n/g, ''),
          },
        ],
      },
    ]
  },
}

 

HTTP 헤더 통신에 사용되는 옵션을 지정하려면 NextJS 의 경우 headers() 를 사용할 수 있습니다. 예제는 NextJS 공식문서를 참고하였고, 제가 적용한 정책의 각 옵션에 대한 설명만 따로 언급하겠습니다 (이렇게만 적용하면 보안상 좋지 못합니다. 일부만 개선하게 되면 어떻게 되는지 확인 해보고 싶어서 몇 가지만 추가해보았습니다).

 

default-src 'self';

기본적으로 모든 콘텐츠(스크립트, 스타일, 이미지 등)는 현재 도메인('self')에서만 로드될 수 있습니다.  다른 특정 -src 지시문이 없는 경우 이 정책이 적용됩니다.

 

script-src 'self' 'unsafe-eval' 'unsafe-inline';

스크립트는 현재 도메인('self')에서만 로드될 수 있습니다. 즉, 타 사에서 로드되는 것을 방지합니다(XSS 차단).

'unsafe-eval'은 eval() 같은 문자열을 코드로 실행하는 함수의 사용을 허용합니다.

'unsafe-inline'은 인라인 스크립트(HTML 내에 직접 작성된 <script> 태그)의 실행을 허용합니다.

 

여기서 'unsafe-eval'과 'unsafe-inline'의 사용은 NextJS의 일부 기능(예: 핫 리로딩, 동적 임포트)을 지원하기 위해 필요할 수 있습니다. 

 

style-src 'self' 'unsafe-inline';

스타일시트는 현재 도메인('self')에서 로드될 수 있습니다.

'unsafe-inline'은 인라인 스타일(<style> 태그 또는 style 속성)의 사용을 허용합니다.

img-src 'self' data: ;

이미지는 현재 도메인('self')에서 로드될 수 있습니다.
'data:'는 데이터 URL을 통한 이미지 로드를 허용합니다(예: base64로 인코딩된 이미지).

 

검사하기

최소한의 CSP 적용과 개선이 필요한 점

CSP 를 적용 후 사이트 배포를 끝 마쳤습니다. 이제 MDN 의 HTTP Observatory Report 검사를 통해서 점수가 올라가는지 확인해보겠습니다.

 

아래 사이트( https://developer.mozilla.org/en-US/observatory)로 접속 후 본인의 도메인 주소를 입력하고 Scan을 눌러 줍니다.

https://developer.mozilla.org/en-US/observatory

 

검사 결과 중에서 CSP 항목이 기존(2024.07.10) -25 에서 5 점이 빠진 -20 이 되었습니다. 옆에는 5점만 개선된 이유와 높이려면 어떻게 해야하는지 권장사항이 나와있습니다. 이 외에도 실제 검사 후 CSP 부분 말고도 보안과 관련한 다양한 측정 항목들을 확인할 수 있으니 꼭 확인해보시길 추천드립니다.

 

개선 후 재검사 | 30점에서 55점으로

일단 추천사항에서 unsfae-inline 의 경우에는 NextJS 내부적으로 쓰이는게 있어서 제거하지는 못하겠지만, data: 는 사용하지 않으니 제거하고, object-src와 sript-src 를 현재 도메인 사이트에서만 사용 가능하게 지정하고, 다시 테스트 해보겠습니다.

 

개선된 옵션 설정은 다음과 같습니다.( https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy 에서 최하단에서 확인 가능) 로 self 는 현재 도메인 사이트에서만 허용한다는 의미 입니다.

const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self';
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`

 

 

결과를 보기에 앞서, 아래는 2024.07.10 에 측정한 결과입니다.

 

 

그리고 2024.07.11 현재는 55점이 되어 C 등급이 되었습니다.

 

그런데 이 개선된 부분은 CSP 가 아닙니다. 저도 예상하지 못한 부분이었는데, CSP 자체는 변동이 없었습니다.

 

 

변경된 부분은  X-Frame-Options 이었습니다. 해당 부분도 -25 점수를 보였기 때문이었는데, 문제가 개선되면서 점수가 올라간 것입니다. 해당 문제가 개선된 이유는 추가한 옵션 중  frame-ancestors 'none'; 이 추가 되었기 때문입니다. 

 

MDN 에서는 CSP 구성 요소 중 하나로 frame-ancestors 를 소개하고 있습니다.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors

 

이렇게 개선된 이유는 X-Frame-Options: DENY와 유사한 효과를 가지는 CSP 옵션이  frame-ancestors 'none'; 이기 때문입니다. 이렇게 설정하게 되면 모든 외부 사이트에서 iframe 등을 이용해 현재 본인의 사이트를 가져가서 사용할 수 없도록 막아줍니다.

 

다만, X-Frame-Options 로 설정하는 것이 레거시 되고 있고, CSP:frame-ancestors 로 지정하는 것이 최근 방식이 되므로 이 점을 유의해서 사용하면 좋을 것 같습니다.

 

 

나가는말

뭔가 흐지부지 들이 마무리 되는 느낌입니다. 다만 해당 과정을 통해서 클릭잭킹이라는 보안상 치명적인 약점을 막을 수 있게 되었습니다.

 

사실 제가 예상했던 시나리와는 다른 결과라서 왜 이러한 변경점이 생겼는가에 대해서 알아보고 정리하다보니 이렇게 되었습니다. 이번 포스팅을 통해서 CSP 가 무엇인지 간략하게 나마 알아보는 시간을 가졌습니다. 사이트 보안에 대해 더 공부해보고 점차 개선하면서 그 결과와 여러 팁들을 계속 공유해보도록 하겠습니다.

 

막간에 한 가지!  다양한 사이트들의 링크를 넣고 검사해보면 많은 사이트가 보안상 취약한 점수를 받고 있는 것을 볼 수 있습니다. 혹시 여러분의 사이트는 안전하신가요? 꼭 한 번 검사해보시길 바랍니다.

 

부족한 글 참고해주셔서 감사드리며, 이만 글을 줄여보겠습니다. 

 

참고자료

Next.org CSP : https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy

MDN CSP : https://developer.mozilla.org/ko/docs/Web/HTTP/CSP

 

 

 

 

반응형