運用 Trusted Types 防範 DOM 型跨網站指令碼攻擊安全漏洞

Krzysztof Kotowicz
Krzysztof Kotowicz

瀏覽器支援

  • Chrome:83。
  • Edge:83。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

當使用者控管的來源 (例如使用者名稱,或從網址片段取得的重新導向網址) 中的資料到達接收器時,就會發生以 DOM 為基礎的跨網站指令碼攻擊 (DOM XSS)。接收器是 eval() 這類函式,或 .innerHTML 這類屬性設定器,可執行任意 JavaScript 程式碼。

DOM XSS 是最常見的網路安全漏洞之一,開發團隊經常會在應用程式中不小心引入這類漏洞。信任類型可提供工具,讓您以預設方式讓危險的網路 API 函式保持安全,進而編寫、進行安全性審查,並確保應用程式不受 DOM XSS 漏洞影響。可在不支援這些類型的瀏覽器上,以 polyfill 的形式提供。

背景

多年以來,DOM XSS 一直是網路安全最常見且最危險的漏洞之一。

跨網站指令碼攻擊分為兩種,部分 XSS 安全漏洞是由伺服器端程式碼造成,這些程式碼會不安全地建立組成網站的 HTML 程式碼。其他問題的根本原因在於用戶端,JavaScript 程式碼會呼叫使用者控制內容的危險函式。

如要防止伺服器端 XSS,請勿透過連結字串產生 HTML。請改用安全的內容自動轉義範本庫,並搭配以 Nonce 為基礎的內容安全性政策,進一步減少錯誤。

如今,瀏覽器也可以使用信任類型,協助防範以用戶端 DOM 為基礎的 XSS。

API 簡介

信任類型會鎖定下列有風險的接收函式,您可能已經知道其中一些,因為瀏覽器供應商和網頁架構已基於安全考量,建議您不要使用這些功能。

信任類型要求您先處理資料,再將資料傳遞至這些接收函式。只使用字串會失敗,因為瀏覽器不知道資料是否可信:

錯誤做法
anElement.innerHTML  = location.href;
啟用 Trusted Types 後,瀏覽器會擲回 TypeError,並防止使用字串的 DOM XSS 接收器。

要表示資料已經安全地處理,請建立一個特殊物件:信任類型。

正確做法
anElement.innerHTML = aTrustedHTML;
  
啟用信任類型後,瀏覽器會接受 TrustedHTML 物件,以便處理預期 HTML 程式碼片段的接收端。其他敏感的接收端也有 TrustedScriptTrustedScriptURL 物件。

信任的類型可大幅減少應用程式的 DOM XSS 攻擊途徑。這項功能可簡化安全性審查作業,並讓您在瀏覽器中執行程式碼編譯、檢查或在執行階段將程式碼打包時,強制執行以類型為依據的安全性檢查。

如何使用信任類型

為內容安全政策違規報告做好準備

您可以部署報表收集器,例如開源的 reporting-api-processorgo-csp-collector,或是使用其中一個商業等價產品。您也可以使用 ReportingObserver,在瀏覽器中新增自訂記錄和偵錯違規:

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

或是新增事件監聽器:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

新增僅供回報的 CSP 標頭

在要遷移至信任類型的文件中,新增下列 HTTP 回應標頭:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

所有違規事項都會回報給 //my-csp-endpoint.example,但網站仍可正常運作。下一節將說明 //my-csp-endpoint.example 的運作方式。

找出 Trusted Types 違規問題

從現在起,每當 Trusted Types 偵測到違規情形,瀏覽器就會將報告傳送至已設定的 report-uri。舉例來說,當應用程式將字串傳送至 innerHTML 時,瀏覽器會傳送下列報表:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

這表示在第 39 行的 https://my.url.example/script.js 中,innerHTML 是使用開頭為 <img src=x 的字串呼叫。這項資訊應該有助於您減少哪些程式碼部分可能導入 DOM XSS,且需要變更。

修正違規事項

您可以透過幾種方式修正 Trusted Type 違規問題。您可以移除違規程式碼使用程式庫建立信任類型政策,或是最後一招建立預設政策

重寫有問題的程式碼

您可能不再需要不符規格的程式碼,或是可以不使用導致違規的函式重新編寫程式碼:

正確做法
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
錯誤做法
el.innerHTML = '<img src=xyz.jpg>';

使用程式庫

部分程式庫已產生可傳遞至接收器函式的信任類型。舉例來說,您可以使用 DOMPurify 消毒 HTML 程式碼片段,移除 XSS 酬載。

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify 支援信任類型,並傳回經過消毒的 HTML,並包裝在 TrustedHTML 物件中,以免瀏覽器產生違規情形。

建立「信任類型」政策

有時您無法移除造成違規的程式碼,而且沒有任何程式庫可用來清理值,並為您建立信任類型。在這種情況下,您可以自行建立信任類型物件。

首先,請建立政策。政策是 Trusted Types 的製造商,可對輸入內容強制執行特定安全性規則:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

此程式碼會建立名為 myEscapePolicy 的政策,該政策可使用其 createHTML() 函式產生 TrustedHTML 物件。定義的規則會將 HTML 轉義 < 字元,以免建立新的 HTML 元素。

使用政策的方式如下:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

使用預設政策

有時您無法變更有問題的程式碼,例如從 CDN 載入第三方程式庫。在這樣的情況下,請使用預設政策

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

在只接受信任類型的接收器中使用字串時,系統會使用名為 default 的政策。

切換為強制執行內容安全政策

當應用程式不再產生違規情形時,您就可以開始強制執行信任類型:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

現在,無論您的網頁應用程式有多複雜,導入 DOM XSS 漏洞的唯一原因就是您政策中的程式碼,而且您可以透過限制政策建立的方式,進一步鎖定這個漏洞。

延伸閱讀