엄격한 콘텐츠 보안 정책 (CSP)으로 교차 사이트 스크립팅 (XSS) 완화

Lukas Weichselbaum
Lukas Weichselbaum

브라우저 지원

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari 15.4.

소스

교차 사이트 스크립팅 (XSS), 웹 앱에 악성 스크립트를 주입하는 기능은 지난 십여 년간 가장 큰 규모의 웹 보안 취약성

콘텐츠 보안 정책 (CSP) XSS를 완화하는 데 도움이 되는 추가 보안 레이어입니다. CSP를 구성하려면 다음 단계를 따르세요. Content-Security-Policy HTTP 헤더를 웹페이지에 추가하고 사용자 에이전트가 해당 페이지에 대해 로드할 수 있는 리소스를 제어합니다.

이 페이지에서는 nonce 또는 해시를 기반으로 CSP를 사용하여 XSS를 완화하는 방법을 설명합니다. 일반적으로 사용되는 호스트-허용 목록 기반 CSP가 페이지를 벗어나는 경우가 많음 대부분의 구성에서 우회할 수 있기 때문에 XSS에 노출되지 않습니다.

핵심 용어: nonce는 한 번만 사용하는 임의의 숫자로서 <script> 태그를 신뢰할 수 있는 항목으로 태그합니다.

핵심 용어: 해시 함수는 입력을 변환하는 수학 함수입니다. 값을 해시라고 하는 압축된 숫자 값으로 변환합니다. 해시를 사용하여 (예: SHA-256) 사용하여 인라인을 표시합니다. <script> 태그를 신뢰할 수 있는 항목으로 태그합니다.

nonce 또는 hash를 기반으로 하는 Content Security Policy는 엄격한 CSP를 따라야 합니다. 애플리케이션에서 엄격한 CSP를 사용하면 HTML을 찾는 공격자가 인젝션 결함은 일반적으로 이를 이용하여 브라우저가 실행되도록 강제하지 못합니다. 악성 스크립트가 포함된 것을 볼 수 있습니다. 엄격한 CSP만 해당되기 때문입니다. 해시된 스크립트 또는 올바른 nonce 값이 있는 스크립트를 따라서 공격자는 올바른 nonce를 모르고 스크립트를 실행할 수 없습니다. 확인할 수 있습니다.

엄격한 CSP를 사용해야 하는 이유는 무엇인가요?

사이트에 이미 script-src www.googleapis.com과 같은 CSP가 있는 경우 크로스 사이트에는 효과적이지 않을 수 있습니다 이 유형의 CSP를 허용 목록 CSP를 참고하세요. 많은 맞춤설정이 필요하며 우회합니다.

암호화 난스 또는 해시를 기반으로 하는 엄격한 CSP는 이러한 함정을 피합니다.

엄격한 CSP 구조

엄격한 기본 콘텐츠 보안 정책은 다음 HTTP 응답 중 하나를 사용합니다. 헤더:

nonce 기반의 엄격한 CSP

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
<ph type="x-smartling-placeholder"></ph>
nonce 기반의 엄격한 CSP 작동 방식

해시 기반 엄격한 CSP

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

다음 속성은 이와 같은 CSP를 '엄격'하게 만듭니다. 이를 통해 보안을 유지합니다.

  • nonce 'nonce-{RANDOM}'를 사용하거나 'sha256-{HASHED_INLINE_SCRIPT}'를 해시합니다. 사이트의 개발자가 실행하도록 신뢰하는 <script> 태그 표시 확인할 수 있습니다
  • 'strict-dynamic'를 설정합니다. 자동으로 nonce 또는 해시 기반 CSP를 배포하는 노력을 줄여 신뢰할 수 있는 스크립트가 만드는 스크립트의 실행을 허용합니다. 또한 대부분의 타사 자바스크립트 라이브러리 및 위젯 사용을 차단 해제합니다.
  • URL 허용 목록에 기반하지 않으므로 일반적인 CSP 우회를 참조하세요.
  • 인라인 이벤트 핸들러 또는 javascript:와 같은 신뢰할 수 없는 인라인 스크립트를 차단합니다. URI.
  • Flash와 같은 위험한 플러그인을 사용 중지하도록 object-src를 제한합니다.
  • <base> 태그 삽입을 차단하도록 base-uri를 제한합니다. 이렇게 하면 상대 URL에서 로드된 스크립트의 위치를 변경하지 못하도록 할 수 있습니다.

엄격한 CSP 채택

엄격한 CSP를 채택하려면 다음을 수행해야 합니다.

  1. 애플리케이션에서 nonce 또는 해시 기반 CSP를 설정해야 하는지 여부를 결정합니다.
  2. 엄격한 CSP 구조 섹션에서 CSP를 복사하여 설정합니다. 애플리케이션 전체에서 응답 헤더로 사용할 수 있습니다.
  3. HTML 템플릿 및 클라이언트 측 코드를 리팩터링하여 CSP와 호환되지 않습니다.
  4. CSP를 배포합니다.

Lighthouse를 사용할 수 있습니다. (--preset=experimental 플래그가 지정된 v7.3.0 이상) 권장사항 감사 사이트에 CSP가 있는지, XSS에 대해 효과적일 수 있을 만큼 엄격합니다.

<ph type="x-smartling-placeholder">
</ph> 등대
  시행 모드에서 CSP를 찾을 수 없다는 경고를 신고합니다.
사이트에 CSP가 없는 경우 Lighthouse에서 이 경고를 표시합니다.

1단계: nonce 기반 또는 해시 기반 CSP가 필요한지 결정

두 가지 유형의 엄격한 CSP가 작동하는 방식은 다음과 같습니다.

nonce 기반 CSP

nonce 기반 CSP를 사용하면 런타임 시 랜덤 숫자를 생성하여 페이지의 모든 스크립트 태그와 연결할 수 있습니다. 공격자 페이지에 악성 스크립트를 포함하거나 실행해서는 안 됩니다. 올바른 랜덤 숫자를 추측해야 합니다 이는 번호가 추측할 수 없으며 모든 응답에 대해 런타임에 새로 생성됩니다.

서버에서 렌더링되는 HTML 페이지에는 nonce 기반 CSP를 사용합니다. 이러한 페이지의 경우 모든 응답에 대해 새로운 랜덤 숫자를 만들 수 있습니다.

해시 기반 CSP

해시 기반 CSP의 경우 모든 인라인 스크립트 태그의 해시가 CSP에 추가됩니다. 스크립트마다 해시가 다릅니다. 공격자는 네트워크에 악성 소프트웨어를 스크립트의 해시가 CSP입니다.

정적으로 제공되는 HTML 페이지 또는 있습니다. 예를 들어 단일 페이지 웹에는 해시 기반 CSP를 사용할 수 있습니다. Angular, React 등의 프레임워크로 빌드된 서버 측 렌더링 없이 정적으로 게재됩니다.

2단계: 엄격한 CSP 설정 및 스크립트 준비

CSP를 설정할 때 다음과 같은 몇 가지 옵션이 있습니다.

  • 보고서 전용 모드 (Content-Security-Policy-Report-Only) 또는 시행 모드 (Content-Security-Policy). 보고서 전용 모드에서는 CSP가 차단되지 않음 따라서 사이트에 아무런 문제도 일어나지 않지만 오류를 확인하고 신고하도록 하는 것입니다. 다음에 있을 때 두 모드 모두 확인할 수 있습니다. 있다면 시행 모드를 사용하면 리소스를 차단하면 초안 CSP에서 차단한 리소스가 페이지가 깨진 것 같습니다. 보고서 전용 모드는 나중에 가장 유용해집니다. 5단계를 참고하세요.
  • 헤더 또는 HTML <meta> 태그입니다. 로컬 개발의 경우 <meta> 태그가 CSP를 조정하고 사이트에 미치는 영향을 빠르게 확인하는 데 편리합니다. 하지만 다음과 같은 예외가 적용됩니다. <ph type="x-smartling-placeholder">
      </ph>
    • 나중에 프로덕션 환경에 CSP를 배포할 때 HTTP 헤더입니다
    • CSP를 보고서 전용 모드로 설정하려면 헤더의 CSP 메타태그에서 보고서 전용 모드를 지원하지 않기 때문입니다.

옵션 A: nonce 기반 CSP

다음 Content-Security-Policy HTTP 응답을 설정합니다. 헤더를 설정합니다.

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

CSP의 nonce 생성

nonce는 페이지 로드당 한 번만 사용되는 임의의 숫자입니다. nonce 기반 CSP는 공격자가 nonce 값을 추측할 수 없는 경우에만 XSS를 완화할 수 있습니다. 가 CSP nonce는 다음과 같아야 합니다.

  • 강력한 암호화가 적용된 임의의 값 (길이가 128비트 이상 권장)
  • 모든 응답에 새로 생성됨
  • Base64 인코딩

다음은 서버 측 프레임워크에서 CSP nonce를 추가하는 방법에 관한 몇 가지 예입니다.

const app = express();

app.get('/', function(request, response) {
  // Generate a new random nonce value for every response.
  const nonce = crypto.randomBytes(16).toString("base64");

  // Set the strict nonce-based CSP response header
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
  response.set("Content-Security-Policy", csp);

  // Every <script> tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

<script> 요소에 nonce 속성 추가

nonce 기반 CSP를 사용하면 모든 <script> 요소는 임의의 nonce와 일치하는 nonce 속성 보유 CSP 헤더에 지정된 값을 갖습니다. 모든 스크립트는 nonce로 표시합니다. 첫 번째 단계는 이러한 속성을 모든 스크립트에 추가하여 CSP는 이를 허용합니다.

옵션 B: 해시 기반 CSP 응답 헤더

다음 Content-Security-Policy HTTP 응답을 설정합니다. 헤더를 설정합니다.

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

여러 인라인 스크립트의 경우 구문은 다음과 같습니다. 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'

<ph type="x-smartling-placeholder">

소스 스크립트를 동적으로 로드하기

CSP 해시는 인라인 스크립트에 대해서만 브라우저에서 지원되므로 인라인 스크립트를 사용하여 모든 타사 스크립트를 동적으로 로드해야 합니다. 원본 스크립트의 해시는 여러 브라우저에서 잘 지원되지 않습니다.

<ph type="x-smartling-placeholder">
</ph>
스크립트를 인라인 처리하는 방법의 예
CSP에서 허용됨
<script>
  var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];

  scripts.forEach(function(scriptUrl) {
    var s = document.createElement('script');
    s.src = scriptUrl;
    s.async = false; // to preserve execution order
    document.head.appendChild(s);
  });
</script>
이 스크립트가 실행되도록 하려면 인라인 스크립트의 해시를 계산해야 합니다. CSP 응답 헤더에 추가하여 {HASHED_INLINE_SCRIPT}를 대체합니다. 자리 표시자. 해시 양을 줄이려면 모든 인라인을 병합할 수 있습니다. 하나의 스크립트로 만들 수 있습니다. 실제 동작을 보려면 및 해당 코드를 포함해야 합니다.
CSP에 의해 차단됨
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
인라인 스크립트만 해싱될 수 있으므로 CSP는 이러한 스크립트를 차단합니다.
<ph type="x-smartling-placeholder">

스크립트 로드 고려사항

인라인 스크립트 예에서는 s.async = false를 추가하여 해당 foobar 전에 실행됩니다. bar이(가) 먼저 로드됩니다. 이 스니펫에서 s.async = false는 은 스크립트가 로드되는 동안 파서를 차단하지 않습니다. 스크립트가 동적으로 추가됩니다. 파서는 스크립트가 실행되는 동안에만 중지됩니다. async 스크립트의 경우에는 이렇게 할 수 있습니다. 그러나 이 스니펫을 사용하면 주의사항:

  • 문서가 완료되기 전에 스크립트 중 하나 또는 둘 다 실행될 수 있습니다. 있습니다. 문서가 준비될 때까지 스크립트가 실행되고 DOMContentLoaded 이벤트가 스크립트를 추가합니다 이로 인해 성능 문제가 발생한다면 스크립트가 충분히 미리 다운로드되지 않으면 페이지 앞부분의 미리 로드 태그를 사용하세요.
  • defer = true에서 아무것도 하지 않습니다. 필요한 경우 필요할 때 스크립트를 수동으로 실행하세요.

3단계: HTML 템플릿 및 클라이언트 측 코드 리팩터링

인라인 이벤트 핸들러 (예: onclick="…", onerror="…") 및 JavaScript URI (<a href="javascript:…">)는 스크립트를 실행하는 데 사용할 수 있습니다. 다시 말해 XSS 버그를 발견한 공격자는 이러한 종류의 HTML을 주입하고 악성 코드를 실행할 수 있습니다. 있습니다. nonce 또는 해시 기반 CSP는 이러한 종류의 마크업 사용을 금지합니다. 사이트에서 이러한 패턴을 사용하는 경우 더 안전한 패턴으로 리팩터링해야 합니다. 대안으로 사용할 수 있습니다.

이전 단계에서 CSP를 사용 설정한 경우 CSP가 호환되지 않는 패턴을 차단할 때마다 콘솔에 표시됩니다.

<ph type="x-smartling-placeholder">
</ph> Chrome 개발자 콘솔의 CSP 위반 신고
차단된 코드의 콘솔 오류

대부분의 경우 해결 방법은 간단합니다.

인라인 이벤트 핸들러 리팩터링

CSP에서 허용됨
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP는 JavaScript를 사용하여 등록된 이벤트 핸들러를 허용합니다.
CSP에 의해 차단됨
<span onclick="doThings();">A thing.</span>
CSP가 인라인 이벤트 핸들러를 차단합니다.

javascript: URI 리팩터링

CSP에서 허용됨
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP는 JavaScript를 사용하여 등록된 이벤트 핸들러를 허용합니다.
CSP에 의해 차단됨
<a href="javascript:linkClicked()">foo</a>
CSP가 JavaScript: URI를 차단합니다.

JavaScript에서 eval() 삭제

애플리케이션에서 eval()를 사용하여 JSON 문자열 직렬화를 JS로 변환하는 경우 객체를 빌드하려면 이러한 인스턴스를 JSON.parse()로 리팩터링해야 합니다. 빠릅니다.

eval()의 사용을 모두 삭제할 수 없는 경우에도 엄격한 nonce 기반을 설정할 수 있습니다. 하지만 'unsafe-eval' CSP 키워드를 사용해야 하므로 정책의 보안이 약간 떨어집니다.

엄격한 CSP에서 이러한 리팩터링의 예시를 더 찾아볼 수 있습니다. codelab:

4단계 (선택사항): 이전 브라우저 버전을 지원하도록 대체 추가하기

브라우저 지원

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari 15.4.

소스

이전 브라우저 버전을 지원해야 하는 경우 다음 단계를 따르세요.

  • strict-dynamic를 사용하려면 https:를 이전 대체 옵션으로 추가해야 합니다. 다운로드할 수 있습니다. 이렇게 하면 다음과 같은 결과가 발생합니다. <ph type="x-smartling-placeholder">
      </ph>
    • strict-dynamic를 지원하는 모든 브라우저는 https: 대체를 무시합니다. 이렇게 해도 정책의 효력이 약화되지 않습니다.
    • 이전 브라우저에서는 외부 소스 스크립트가 HTTPS 출처입니다 엄격한 CSP보다 보안은 취약하지만 그래도 javascript: URI 삽입과 같은 일반적인 XSS 원인을 방지합니다.
  • 매우 오래된 브라우저 버전 (4년 이상)과의 호환성을 위해 unsafe-inline를 대체합니다. 모든 최신 브라우저가 unsafe-inline을(를) 무시합니다. CSP nonce 또는 hash가 있는 경우
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';

5단계: CSP 배포

CSP가 서버에서 적법한 스크립트를 차단하지 않는지 확인한 후 로컬 개발 환경에서 CSP를 스테이징에 배포한 다음 프로덕션 환경:

  1. (선택사항) 다음을 사용하여 CSP를 보고서 전용 모드로 배포합니다. Content-Security-Policy-Report-Only 헤더. 보고서 전용 모드를 사용하면 새로운 CSP와 같은 브레이킹 체인지를 테스트한 후에 CSP 제한을 적용하기 시작해야 합니다. 보고서 전용 모드에서는 CSP가 다음 기능을 지원하지 않습니다. 앱 동작에 영향을 미치지만 브라우저는 여전히 콘솔 오류를 발생시킵니다. CSP와 호환되지 않는 패턴이 발견되는 경우 최종 사용자에게 어떤 문제가 있었는지 확인할 수 있습니다. 자세한 내용은 자세한 내용은 Reporting API를 참고하세요.
  2. CSP가 최종 사용자에게 사이트를 방해하지 않는다고 확신하는 경우 Content-Security-Policy 응답 헤더를 사용하여 CSP를 배포합니다. HTTP 헤더 서버 측을 사용하여 CSP를 설정하는 것이 좋습니다. <meta> 태그보다 안전해야 합니다. 이 단계를 완료하면 CSP가 시작됩니다. XSS로부터 앱을 보호할 수 있습니다.

제한사항

엄격한 CSP는 일반적으로 완화할 수 있습니다 대부분의 경우 CSP는 다음과 같은 방법으로 공격 표면을 크게 줄입니다. javascript: URI와 같은 위험한 패턴 거부 하지만 'strict-dynamic' 유무와 관계없이 사용 중인 CSP의 CSP가 앱을 보호하지 않는 경우입니다.

  • 스크립트를 nonce 있지만 본문이나 <script> 요소의 src 매개변수
  • 동적으로 생성된 스크립트의 위치에 삽입이 있는 경우 (document.createElement('script')) - 라이브러리 함수 포함 인수 값을 기반으로 script DOM 노드를 만듭니다. 이 jQuery의 .html(), .get(), jQuery의 .post() < 3.0.
  • 이전 AngularJS 애플리케이션에 템플릿 삽입이 있는지 여부 공격자 AngularJS 템플릿에 삽입할 수 있는 API는 이를 사용하여 임의의 JavaScript를 실행합니다.
  • 정책에 'unsafe-eval', eval()에 삽입 setTimeout() 및 거의 사용되지 않는 몇 가지 API가 있습니다.

개발자와 보안 엔지니어는 패턴을 발견할 수 있습니다. 자세한 내용은 콘텐츠 보안 정책: 강화와 완화 사이의 성공적인 혼란에서 이러한 케이스를 확인하시기 바랍니다.

추가 자료