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

Krzysztof Kotowicz
Krzysztof Kotowicz

瀏覽器支援

  • 83
  • 83
  • x
  • x

來源

以 DOM 為基礎的跨網站指令碼攻擊 (DOM XSS) 是指來自使用者所控制「來源」的資料 (例如使用者名稱或從網址片段擷取的重新導向網址) 的「接收器」,也就是 eval() 之類的函式,或是可執行任意 JavaScript 程式碼的 .innerHTML 等屬性 setter。

DOM XSS 是最常見的網路安全漏洞之一,開發團隊往往會不小心在應用程式中引入這個安全漏洞。Trusted Types 會將危險的 Web API 功能納入安全考量,讓員工可以編寫和安全性審查,並防止應用程式受到 DOM XSS 安全漏洞。可在不支援這些類型的瀏覽器上,以 polyfill 的形式提供。

背景

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

跨網站指令碼有兩種類型。部分 XSS 安全漏洞的原因,可能是伺服器端程式碼用不安全的方式建立網站的 HTML 程式碼。有些則是用戶端的根本原因,而 JavaScript 程式碼會透過使用者控管的內容呼叫危險函式。

防止伺服器端 XSS,請勿藉由串連字串來產生 HTML。因此,請改用安全的自動逸出範本程式庫,以及以 Nonce 為基礎的內容安全性政策,藉此防範其他錯誤。

現在瀏覽器也能使用信任的類型,防範用戶端以 DOM 為基礎的 XSS。

API 簡介

受信任的類型會鎖定下列有風險的接收器功能。您可能已經認識其中一些機制,因為瀏覽器廠商和網路架構已基於安全考量,而停用這些功能。

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

錯誤做法
anElement.innerHTML  = location.href;
啟用「可信任類型」後,瀏覽器會擲回 TypeError,避免使用含有字串的 DOM XSS 接收器。

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

正確做法
anElement.innerHTML = aTrustedHTML;
  
啟用「信任的類型」之後,瀏覽器就會接受 TrustedHTML 物件,以便接收需要 HTML 程式碼片段的接收器。另外,其他機密接收器也提供 TrustedScriptTrustedScriptURL 物件。

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

如何使用「Trusted Types」

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

您可以部署開放原始碼 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 違規事項

從現在起,只要信任的類型偵測到違規事件,瀏覽器就會傳送報告至設定的 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 中,系統會使用 <img src=x 開頭的字串呼叫 innerHTML。這項資訊應該有助於您減少哪些程式碼部分可能導入 DOM XSS,且需要變更。

修正違規問題

修正「信任類型」違規問題時,您可以移除違規的程式碼使用程式庫建立信任類型政策,或者建立預設政策

重新編寫違規程式碼

您可能已經不再需要不符規定的程式碼,也有可能是在沒有造成違規的函式的情況下重寫:

正確做法
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 支援 Trusted Types,並傳回 TrustedHTML 物件中包裝的經過清理 HTML,這樣瀏覽器就不會產生違規行為。

建立信任類型政策

有時您無法移除導致違規的程式碼,也沒有程式庫能掃除該值並為您建立 Trusted Type。在這種情況下,您可以自行建立信任類型物件。

首先,請建立政策。政策是「受信任的類型」的工廠,會在輸入時強制執行特定安全性規則:

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 漏洞的唯一原因就是您政策中的程式碼,而您可以透過限制政策建立的方式,進一步鎖定這個漏洞。

其他資訊