신뢰할 수 있는 유형으로 DOM 기반 XSS 공격 방지

신뢰할 수 있는 유형은 '위험한' 브라우저 API(싱크라고 함)에 전달되는 데이터를 먼저 특정 유형(예: TrustedHTML)으로 변환하도록 요구하여 DOM 기반 교차 사이트 스크립팅(DOM XSS)을 방지하는 보안 기능입니다.

기본적으로 많은 DOM API (예: innerHTML 또는 document.write)는 원시 문자열을 허용합니다. 문자열에 신뢰할 수 없는 소스의 악성 스크립트가 포함된 경우 실행될 수 있습니다. 신뢰할 수 있는 유형은 이 동작을 변경합니다. CSP를 통해 사용 설정되면 이러한 싱크는 승인된 정책에 의해 생성된 TrustedHTML 객체만 허용합니다.

주요 구현 세부정보

신뢰할 수 있는 유형을 사용하려면 다음 네 가지 주요 단계를 따르세요.

  1. 문자열을 정제하거나 변환하는 방법을 지정하는 보안 정책을 정의합니다.
  2. 이 정책을 사용하여 원시 문자열에서 신뢰할 수 있는 유형 객체를 만듭니다.
  3. DOM 싱크에 쓸 때 신뢰할 수 있는 유형 객체를 사용합니다.
  4. 콘텐츠 보안 정책 (CSP)을 사용하여 신뢰할 수 있는 유형을 적용합니다.

1. 정책 만들기

정책은 콘텐츠를 정제하는 규칙의 집합입니다. trustedTypes.createPolicy()를 사용하여 만들며, 일반적으로 DOMPurify와 같은 살균 라이브러리 또는 애플리케이션의 HTML 이스케이프 규칙에 위임합니다.

import DOMPurify from 'dompurify';

const myPolicy = trustedTypes.createPolicy('sanitize-html', {
  createHTML: (input) =>
    // Return a sanitized string; createPolicy() wraps it in a TrustedHTML object.
    // Example using DOMPurify:
    DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
});

// Example using HTML escaping (for environments without third-party sanitizers):
const escapePolicy = trustedTypes.createPolicy('escape-html', {
  createHTML: (input) =>
    String(input)
      .replaceAll('&', '&')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#39;'),
});

2. 정책 사용

싱크에 문자열을 직접 전달하는 대신 정책의 생성 메서드를 호출합니다.

const untrustedInput = '<p>Hello, <strong>world</strong>!</p><img src="x" onerror="alert(1)">';

// In an enforced environment, this would throw a TypeError:
// element.innerHTML = untrustedInput;

// This returns a TrustedHTML object with unsafe elements and attributes removed:
const cleanHTML = myPolicy.createHTML(untrustedInput);
// Result: "<p>Hello, <strong>world</strong>!</p><img src=\"x\">"

3. 신뢰할 수 있는 유형을 사용하도록 싱크 전환

싱크에 쓰는 각 인스턴스를 업데이트하여 먼저 신뢰할 수 있는 유형 객체로 변환합니다. 코드를 업데이트해야 하는 위치를 찾으려면 TrustedType이 아닌 문자열로 싱크가 작성될 때 로깅하는 임시 default 정책을 정의하면 됩니다.

trustedTypes.createPolicy("default", {
  createHTML(value) {
    console.warn("String passed instead of Trusted Type.");
    return DOMPurify.sanitize(value, { RETURN_TRUSTED_TYPE: false });
  },
});

신뢰할 수 있는 유형을 지원하는 일반적인 싱크는 다음과 같습니다.

  • HTML: element.innerHTML, element.outerHTML, document.write(), element.setHTMLUnsafe(), Document.parseHTMLUnsafe()
  • 스크립트: <script> 텍스트 콘텐츠, eval()
  • ScriptURL: <script src>, Worker(), SharedWorker()
// Assign the TrustedHTML object.
element.innerHTML = cleanHTML;

'안전한' HTML 정리 메서드 (element.setHTML() 및 Document.parseHTML(), 신뢰할 수 없는 HTML 정리에서 다룸)는 항상 잠재적인 XSS 공격을 기본적으로 정리하므로 신뢰할 수 있는 유형 정책이 필요하지 않습니다.

4. CSP로 신뢰할 수 있는 유형 적용

애플리케이션이 중단되지 않도록 신뢰할 수 있는 유형을 점진적으로 출시해야 합니다. 전체 시행은 가장 강력한 보호를 제공하지만 모든 싱크 쓰기가 이전되지 않은 경우 사이트가 손상될 위험이 있습니다.

기본 요건

  • 프레임워크 협력: 애플리케이션의 프레임워크 (또는 DOM 싱크에 쓰는 서드 파티 라이브러리)가 TrustedHTML 또는 TrustedScript 값을 생성하지 않는 경우 해당 코드를 중단하지 않고는 신뢰할 수 있는 유형을 적용할 수 없습니다. 출시를 시작하기 전에 프레임워크 지원을 감사합니다.
  • 싱크 리팩터링: 신뢰할 수 있는 유형 정책을 사용하도록 위험한 싱크를 식별하고 리팩터링했는지 확인합니다.

출시 전략

  1. 보고 전용: 먼저 Content-Security-Policy-Report-Only 헤더를 사용하여 출시합니다. 이렇게 하면 실제로 쓰기를 차단하지 않고도 보고 엔드포인트에 위반사항이 로깅됩니다.

    Content-Security-Policy-Report-Only: require-trusted-types-for 'script';
    
  2. 시정 조치: 정책을 통합하고 위반 신고가 0으로 떨어지면 전체 시정 조치로 전환합니다.

    Content-Security-Policy: require-trusted-types-for 'script';
    

생성이 허용되는 정책을 제한할 수도 있습니다(sanitize-html 또는 escape-html와 같은 정책 이름 나열, 정책이 DOMPurify.sanitize()에 위임하는 경우 dompurify 포함(내부적으로 dompurify 정책 생성), 마이그레이션 중에 기본 정책을 등록하는 경우 default 포함).

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;

이 CSP가 적용되면 신뢰할 수 있는 유형을 사용하지 않는 싱크에 쓰기를 시도하면 오류가 발생합니다. 또한 trusted-types에 나열되지 않은 이름으로 신뢰할 수 있는 유형 정책을 만들려고 하면 오류가 발생합니다.

이 CSP를 문서의 <head>에 <meta> 태그로 설정할 수도 있지만, 이 경우 보안이 취약해질 수 있습니다.

<!-- First child of `<head>`, as earlier tags will not enforce this CSP. -->
<meta
  http-equiv="Content-Security-Policy"
  content="require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;"
/>

대체 전략

아직 신뢰할 수 있는 유형을 지원하지 않는 브라우저의 경우 최소한의 'tinyfill'을 사용하여 코드가 손상되지 않도록 하면서도 살균을 제공할 수 있습니다.

if (!window.trustedTypes) {
  window.trustedTypes = {
    createPolicy: (name, rules) => rules,
  };
}

이렇게 하면 지금 바로 신뢰할 수 있는 유형 패턴을 사용하여 코드를 작성할 수 있습니다. 지원되는 브라우저는 유형을 적용하는 반면, 이전 브라우저는 모의 정책을 통해 정제 로직을 계속 실행합니다.

브라우저가 신뢰할 수 있는 유형을 지원하지 않는 경우 TrustedHTML 객체를 문자열로 처리하거나 (tinyfill을 사용하는 경우) 싱크 수준에서 시행 검사를 하지 않습니다.