Chrome
即將在 83
版本新增一個可信類型(Trusted types
),其號稱這一特性可以全面消除 DOM XSS
,為此我連夜分析了一波,下面我就帶大家來具體看一下這個特性:
多年來,
DOM XSS
一直是最普遍且最危險的 Web
安全漏洞之一。根據去年發布的 Imperva
報告,XSS
漏洞是 2014
年、2015
年、2016
年和 2017
年最普遍的基于 Web
的攻擊形式。2018
年的漏洞冠軍被 SQL
注入拿到了,XSS
漏洞仍然排在第二位。
平時大家也經常忽略 XSS
漏洞,因為它們并不總是對訪問站點的用戶造成直接損害。然而,它們往往是復雜的漏洞利用程序中的第一個踏腳石,可以促進更具破壞性的攻擊。在許多情況下,消除 XSS 攻擊可以使用戶免受更復雜的攻擊。
XSS
有兩種不同的類型,某些 XSS
漏洞是由服務器端代碼導致的,這些代碼不安全地創建了構成網站的 HTML
代碼。其他問題則在客戶端上導致的,比如開發者接收用戶可以控制的內容來調用一些危險的 JavaScript
函數。
為了防止服務器端 XSS
,不要通過連接字符串來生成 HTML
,而是使用安全的上下文自動轉義模板庫。當你避免不了使用這種方式時,可以使用 nonce-based
的安全策略來對其進行額外的防御。
現在,瀏覽器可以使用 Trusted Types
來防御客戶端 XSS
。
Trusted Types
的工作方式就是鎖定以下危險函數的接收參數,如果是不安全的,就直接阻止。
您可能已經有所耳聞,因為出于安全原因,瀏覽器和
Web
框架已經開始建議你遠離下面這些功能。
腳本操作:
<script src>
和設置 <script>
元素的文本內容。
從字符串生成 HTML
:
innerHTML,outerHTML,insertAdjacentHTML, <iframe> srcdoc, document.write,document.writeln,和DOMParser.parseFromString
執行插件內容:
<embed src>,<object data>和<object codebase>
運行 JavaScript
代碼的編譯:
eval,setTimeout,setInterval,new Function()
Trusted Types
為開發者提供了一個內容安全策略,你可以在你的 CSP
配置中增加下面的配置:
Content-Security-Policy: trusted-types;
當你開啟這個配置之后,如果你頁面中執行了下面這樣的代碼,瀏覽器將引發 TypeError
并阻止將 DOM XSS
接收器與字符串一起使用 :
document.innerHTML = '<img src=code秘密花園.jpg>';
如果你用下面這種安全的方式創建了 DOM
,瀏覽器則不會拋出錯誤:
el.textContent = '';
const img = document.createElement('img');
img.src = 'code秘密花園.jpg';
el.appendChild(img);
或者,這個危險的 innerHTML
方法可以接受一個受信任的類型字符串,瀏覽器也不會報錯,下面我們來看看如何使用 Trusted Types
創建受信任的字符串:
一些庫已經生成了可傳遞給接收器函數的可信類型。例如,您可以使用 DOMPurify
過濾 HTML
代碼段:
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true);
DOMPurify
支持可信類型,并將返回包裝在 TrustedHTML
對象中的經過過濾的 HTML
,以使瀏覽器不會產生沖突。
如果你的瀏覽器支持了 trustedTypes
,你可以使用 trustedTypes
創建一個合適的過濾策略,這個策略就是創建信任字符串的工廠,你可以在這個策略上實現你自己的安全規則:
if (window.trustedTypes && trustedTypes.createPolicy) {
const escapeHTMLPolicy = trustedTypes.createPolicy('conardEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
這段代碼創建了一個稱為 conardEscapePolicy
的策略 ,可以通過 createHTML()
函數創建受信任的字符串。定義的規則將使用 HTML
轉義 <
字符,以防止創建新的 HTML
元素。
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
同樣的,你可以在你的 CSP
配置中指定你實現的信任策略,未被指定的策略同樣會被瀏覽器阻止:
Content-Security-Policy: trusted-types <policyName>;
Content-Security-Policy: trusted-types <policyName> <policyName> 'allow-duplicates';
有時你無法更改有問題的代碼。例如,從 CDN
加載第三方庫,在這種情況下可以使用默認策略:
if (window.trustedTypes && trustedTypes.createPolicy) {
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true});
});
}
謹慎使用這個默認策略,因為它并不一定適用于你的過濾場景,盡量實現自己的安全規則。
不過大家先別著急用,這個特性最早將于不久后的
Chrome 83
版本和大家見面,因此目前大部分瀏覽器還尚未支持。不過,具有較大的 XSS
風險的站點建議大家都支持一波~
不過這個 CSP
配置一定要謹慎的開啟,你的第一次更改不一定是全面的,如果你直接開啟了可能會導致大量代碼被瀏覽器阻止,所以建議還是先開啟 Report-Only
,這樣被瀏覽器阻止的代碼就會先被上報上來,你可以根據上報的日志不斷完善你代碼中的安全規則:
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //csp.reporter.example
比如,你可能會看到下面格式的上報結果:
{
"csp-report": {
"document-uri": "https://csp.reporter.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://csp.reporter.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
這表示在 https://csp.reporter.example/script.js
第 39
行 innerHTML
中以 <img src=x
開頭的字符串被阻止調用 。
最多閱讀