安全地將外部內容插入頁面
有時您可能希望或需要將內容從外部源包含到您的擴充套件程式中。但是,存在該源可能嵌入惡意指令碼的風險——這些指令碼可能由源的開發者或惡意的第三方新增。
以 RSS 閱讀器為例。您不知道您的擴充套件程式將開啟哪些 RSS feed,也無法控制這些 RSS feed 的內容。因此,使用者有可能訂閱一個 feed,其中 feed 項的標題包含一個指令碼。這可能很簡單,比如在 <script></script> 標籤中包含 JavaScript 程式碼。如果您提取了標題,假設它是純文字,並將其新增到您的擴充套件程式建立的頁面的 DOM 中,那麼您的使用者現在將在其瀏覽器中執行一個未知的指令碼。因此,需要採取措施避免將任意文字評估為 HTML。
您還需要記住,擴充套件程式具有特權上下文,例如在後臺指令碼和內容指令碼中。在最壞的情況下,嵌入的指令碼可能會在這些上下文之一中執行,這種情況稱為許可權升級。這種情況可能使攻擊者能夠透過允許注入程式碼的網站訪問關鍵使用者資料(如密碼、瀏覽器歷史記錄或瀏覽行為)來讓使用者的瀏覽器遭受遠端攻擊。
本文探討了如何安全地處理遠端資料並將其新增到 DOM。
處理任意字串
在處理字串時,有幾種推薦的選項可以安全地將它們新增到頁面:標準的 DOM 節點建立方法或 jQuery。
用於節點建立和安全文字插入的 DOM API
要輕量級且安全地插入字串,請使用原生 DOM API:使用 document.createElement 建立元素,並僅使用 Element.setAttribute 設定已驗證的、不可執行的屬性。要新增文字內容,請使用 textContent 屬性。一種安全的方法是單獨建立節點並使用 textContent 分配其內容。
let data = JSON.parse(responseText);
let div = document.createElement("div");
div.className = data.className;
div.textContent = `Your favorite color is now ${data.color}`;
addonElement.appendChild(div);
這種方法是安全的,因為 .textContent 會自動轉義 data.color 中的任何遠端 HTML。
但是,請注意,您可以使用不安全的原生方法。請看以下程式碼
let data = JSON.parse(responseText);
addonElement.innerHTML = `<div class='${data.className}'>Your favorite color is now ${data.color}</div>`;
在這裡,data.className 或 data.color 的內容可能包含 HTML,可以提前關閉標籤,插入任意額外的 HTML 內容,然後開啟另一個標籤。
jQuery
使用 jQuery 時,attr() 和 text() 等函式在將內容新增到 DOM 時會對其進行轉義。因此,上面“喜歡的顏色”的示例,用 jQuery 實現,看起來如下
let node = $("</div>");
node.addClass(data.className);
node.text(`Your favorite color is now ${data.color}`);
處理 HTML 內容
在處理已知為 HTML 的外部來源內容時,在將其新增到頁面之前對其進行清理至關重要。清理 HTML 的最佳實踐是使用 HTML 清理庫或具有 HTML 清理功能的模板引擎。在本節中,我們將介紹一些合適的工具以及如何使用它們。
HTML 清理
HTML 清理庫會刪除任何可能導致指令碼執行的內容,因此您可以安全地將完整的 HTML 節點集從遠端源注入到您的 DOM 中。DOMPurify 經過多位安全專家審查,是擴充套件程式中用於此任務的合適庫。
對於生產環境,DOMPurify 提供了一個精簡版本:purify.min.js。您可以根據擴充套件程式的需要使用此指令碼。例如,您可以將其新增為內容指令碼
"content_scripts": [
{
"matches" : ["<all_urls>"],
"js": ["purify.min.js", "my-injection-script.js"]
}
]
然後,在 my-injection-script.js 中,您可以讀取外部 HTML,對其進行清理,然後將其新增到頁面的 DOM 中。
let elem = document.createElement("div");
let cleanHTML = DOMPurify.sanitize(externalHTML);
elem.innerHTML = cleanHTML;
您可以使用任何方法將清理後的 HTML 新增到您的 DOM 中,例如 jQuery 的 .html() 函式。但請記住,在這種情況下需要使用 SAFE_FOR_JQUERY 標誌。
let elem = $("<div/>");
let cleanHTML = DOMPurify.sanitize(externalHTML, { SAFE_FOR_JQUERY: true });
elem.html(cleanHTML);
模板引擎
另一種常見的模式是為頁面建立一個本地 HTML 模板,並使用遠端值來填補空白。雖然這種方法通常是可以接受的,但應注意避免使用允許插入可執行程式碼的構造。當模板引擎使用將原始 HTML 插入文件的構造時,可能會發生這種情況。如果用於插入原始 HTML 的變數來自遠端源,則它會受到介紹中提到的相同安全風險的影響。
例如,在使用 mustache 模板時,您必須使用雙大括號 {{variable}},它會轉義任何 HTML。必須避免使用三大括號 {{{variable}}},因為它會注入原始 HTML 字串並可能將可執行程式碼新增到您的模板中。Handlebars 的工作方式類似,雙大括號 {{variable}} 中的變數會被轉義。而三層大括號中的變數將保持原樣,必須避免。此外,如果您使用 Handlebars.SafeString 建立 Handlebars 助手,請使用 Handlebars.escapeExpression() 轉義傳遞給助手的任何動態引數。這是必需的,因為 Handlebars.SafeString 生成的變數被認為是安全的,並且在用雙大括號插入時不會被轉義。
其他模板系統也有類似的構造,需要同樣小心地處理。
延伸閱讀
有關此主題的更多資訊,請參閱以下文章