在現代的Web 應用中,動態生成和渲染 HTML 字符串是很常見的需求。然而,不正確地渲染HTML字符串可能會導致安全漏洞,例如跨站腳本攻擊(XSS)。為了確保應用的安全性,我們需要采取一些措施來在安全的環境下渲染HTML字符串。本文將介紹一些安全渲染 HTML 字符串的最佳實踐,以幫助你有效地避免潛在的安全風險。
一、常見渲染方式
首先來看一下如何在 HTML、React、Vue、Angular 中渲染HTML字符串。
HTML
在HTML中渲染HTML字符串,可以使用原生JavaScript的innerHTML屬性或者創建元素節點并使用appendChild()方法來實現。
(1)使用innerHTML屬性:可以通過獲取要渲染HTML的目標元素,并將HTML字符串賦值給其innerHTML屬性來渲染HTML字符串。例如:
<div id="targetElement"></div><script>const htmlString = "<h1>Hello, World!</h1>";document.getElementById("targetElement").innerHTML = htmlString;
</script>
這將在<div id="targetElement"></div>內部渲染出<h1>Hello, World!</h1>。
(2)創建元素節點和appendChild()方法:可以使用document.createElement()方法創建元素節點,并使用appendChild()方法將該節點添加到父元素中。例如:
<div id="targetElement"></div><script>const htmlString = "<h1>Hello, World!</h1>";const parentElement = document.getElementById("targetElement");const tempElement = document.createElement("div");tempElement.innerHTML = htmlString;while (tempElement.firstChild) {parentElement.appendChild(tempElement.firstChild);}
</script>
這將在<div id="targetElement"></div>內部渲染出<h1>Hello, World!</h1>。
React
可以通過使用dangerouslySetInnerHTML屬性在 React 中渲染HTML字符串。但是,正如這個屬性的名字所言,它存在安全風險,HTML 不會被轉義,可能會導致XSS問題,因此請慎重使用。
import React from 'react';const MyComponent = () => {const htmlString = '<p>Hello, <strong>React</strong>!</p>';return (<div dangerouslySetInnerHTML={{ __html: htmlString }} />
);
}export default MyComponent;
這里將要渲染的HTML字符串存儲在htmlString變量中,并將其傳遞給dangerouslySetInnerHTML屬性的__html屬性。React會將該字符串作為HTML內容插入到被渲染的組件中。
Vue
可以使用v-html指令在Vue中渲染HTML字符串。與在React中使用dangerouslySetInnerHTML類似,使用v-html時需要格外小心。
<template><div v-html="htmlString"></div>
</template><script>
export default {data() {return {htmlString: '<p>Hello, <strong>Vue</strong>!</p>',};},
};
</script>
這里將要渲染的HTML字符串存儲在htmlString中,并通過v-html指令將其綁定到需要渲染的元素上(這里是<div>)。Vue會將htmlString中的字符串解析為HTML,并將其插入到被渲染的元素中。
Angular
可以使用[innerHTML]屬性在Angular中渲染 HTML 字符串。
<div [innerHTML]="htmlString"></div>
這里將要渲染的HTML字符串存儲在名為htmlString的變量中,并將其綁定到[innerHTML]屬性上。Angular會將htmlString中的字符串解析為HTML,并將其插入到相應的DOM節點中。
與其他框架相似,使用[innerHTML]屬性綁定時要特別小心。確保渲染的HTML字符串是可靠和安全的,避免直接從用戶輸入或不受信任的來源獲取HTML字符串,以防止XSS攻擊等安全問題。
另外,Angular也提供了一些內置的安全機制來幫助保護應用免受安全威脅。例如,通過使用Angular的內置管道(如DomSanitizer)對HTML字符串進行轉義和驗證,可以提高應用的安全性。
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';@Component({selector: 'app-example',template: `<div [innerHTML]="getSafeHtml()"></div>`,
})
export class ExampleComponent {htmlString: string = '<p>Hello, <strong>Angular</strong>!</p>';constructor(private sanitizer: DomSanitizer) {}getSafeHtml(): SafeHtml {return this.sanitizer.bypassSecurityTrustHtml(this.htmlString);}
}
這里首先導入DomSanitizer和SafeHtml,這是Angular的內置服務和類型。然后,在組件中使用DomSanitizer通過調用bypassSecurityTrustHtml()方法對HTML字符串進行轉義和驗證。最后,將返回的SafeHtml對象綁定到[innerHTML]屬性上,以進行安全的HTML渲染。
通過使用DomSanitizer服務,Angular會對HTML字符串進行安全檢查,并只允許受信任的內容進行渲染,從而減少潛在的安全風險。
注意,在使用DomSanitizer時,確保只對受信任和經過驗證的HTML字符串進行操作,并避免直接從用戶輸入或不受信任的來源獲取HTML字符串。這樣可以確保應用的安全性,并防止潛在的XSS攻擊等安全問題。
二、HTML Sanitizer API
從上面的例子中可以看到,在常見的框架以及在HTML中渲染HTML字符串都存在一定的安全風險。當將用戶提供的或不受信任的HTML字符串直接渲染到應用中時,可能會導致跨站腳本攻擊(XSS)等安全漏洞。因此,在處理和渲染HTML字符串時,需要采取適當的安全措施來防止潛在的安全問題。
那 HTML 中有沒有方法可以讓我們安全的渲染 HTML 字符串呢?有,它就是 HTML Sanitizer API。不過這個 API 目前仍然是實驗性的,在主流瀏覽器都支持之前,盡量不要在生產環境使用。下面先來看看這個 API 是怎么用的,為未來該 API 普遍可用做準備。
是什么?
HTML Sanitizer API 在 2021 年初的草案規范中首次被宣布。它為網站上動態更新的HTML提供原生瀏覽器支持,可以從中刪除惡意代碼。可以使用 HTML Sanitizer API 在將不安全的 HTML 字符串和?Document?或?DocumentFragment?對象插入到 DOM 中之前對其進行清理和凈化。
構建獨立的 API 來進行清理的主要目標是:
- 減少 Web 應用中跨站腳本攻擊的攻擊面。
- 保證 HTML 輸出在當前用戶代理中的安全性。
- 提高清理器的可用性并使其更方便使用。
HTML Sanitizer API 的出現旨在提供一種方便且安全的方式來處理和凈化 HTML,以減少潛在的安全風險,并提高用戶代理的安全性。
Sanitizer API 帶來了一系列新功能,用于字符串的凈化過程:
- 用戶輸入的凈化:該 API 的主要功能是接受并將字符串轉換為更安全的形式。這些轉換后的字符串不會意外執行 JavaScript,并確保您的應用程序受到跨站腳本攻擊的保護。
- 瀏覽器維護:此庫已預先安裝在瀏覽器中,并將在發現錯誤或新的攻擊向量時進行更新。因此,現在擁有了一個內置的凈化器,無需導入任何外部庫。
- 安全且簡單易用:將凈化操作轉移到瀏覽器中使其更加便捷、安全和快速。由于瀏覽器已經具有強大而安全的解析器,它知道如何處理 DOM 中的每個活動元素。與瀏覽器相比,用 JavaScript 開發的外部解析器可能成本較高,并且很快就會過時。
怎么用?
使用 Sanitizer API 非常簡單,只需使用?Sanitizer()?構造函數實例化?Sanitizer?類,并配置實例即可。
對于數據的凈化,該 API 提供了三個基本方法。讓我們看看應該如何以及何時使用它們。
- 使用隱含上下文對字符串進行凈化?
Element.setHTML()?用于解析和凈化字符串,并立即將其插入到 DOM 中。這適用于已知目標 DOM 元素并且 HTML 內容以字符串形式存在的情況。
const $div = document.querySelector('div');
const user_input = `<em>Hello There</em><img src="" notallow=alert(0)>`;
const sanitizer = new Sanitizer() // Our Sanitizer$div.setHTML(user_input, sanitizer); // <div><em>Hello There</em><img src=""></div>
這里想將?user_string?中的 HTML 插入到?id?為?target?的目標元素中。也就是說,希望實現得到與?target.innerHTML = value?相同的效果,但避免 XSS 風險。
- 使用給定上下文對字符串進行凈化
Sanitizer.sanitizeFor()?用于解析、凈化和準備字符串,以便稍后添加到 DOM 中。當 HTML 內容以字符串形式存在,并且已知目標 DOM 元素類型(例如?div、span)時,此方法最適用。
const user_input = `<em>Hello There</em><img src="" notallow=alert(0)>`
const sanitizer = new Sanitizer()sanitizer.sanitizeFor("div", user_input) // HTMLDivElement <div>
Sanitizer.sanitizeFor()的第一個參數描述了此結果所用于的節點類型。
在使用?sanitizeFor()?方法時,解析 HTML 字符串的結果取決于其所在的上下文/元素。例如,如果將包含?<td>?元素的 HTML 字符串插入到?<table>?元素中,則是允許的。但如果將其插入到?<div>?元素中,它將被移除。因此,在使用?Sanitizer.sanitizeFor()?方法時,必須將目標元素的標簽指定為參數。
sanitizeFor(element, input)
這里也可以使用 HTML 元素中的?.innerHTML?來獲取字符串形式的清理結果。
sanitizer.sanitizeFor("div", user_input).innerHTML // <em>Hello There</em><img src="">
- 使用節點進行凈化
當已經有一個用戶可控的?DocumentFragment?時,可以使用?Sanitizer.sanitize()?方法對 DOM 樹節點進行凈化。
const sanitizer = new Sanitizer()
const $userDiv = ...;
$div.replaceChildren(s.sanitize($userDiv));
除此之外,Sanitizer API 還通過刪除和過濾屬性和標簽來修改 HTML 字符串。例如,Sanitizer API:
- 刪除某些標簽(script、marquee、head、frame、menu、object 等),但保留內容標簽。
- 刪除大多數屬性。只會保留?<a>?標簽上的?href?和?<td>、<th>?標簽上的?colspans,其他屬性將被刪除。
- 過濾可能引起腳本執行的字符串。
自定義
默認情況下,Sanitizer 實例僅用于防止 XSS 攻擊。但是,在某些情況下,可能需要自定義配置的清理器。接下來,下面來看看如何自定義 Sanitizer API。
如果想創建自定義的清理器配置,只需要創建一個配置對象,并在初始化 Sanitizer API 時將其傳遞給構造函數即可。
const config = {allowElements: [],blockElements: [],dropElements: [],allowAttributes: {},dropAttributes: {},allowCustomElements: true,allowComments: true
};
// 清理結果由配置定制
new Sanitizer(config)
以下配置參數定義了清理器應如何處理給定元素的凈化結果。
- allowElements:指定清理器應保留在輸入中的元素。
- blockElements:指定清理器應從輸入中刪除但保留其子元素的元素。
- dropElements:指定清理器應從輸入中刪除,包括其子元素在內的元素。
const str = `hello <b><i>there</i></b>`new Sanitizer().sanitizeFor("div", str)
// <div>hello <b><i>there</i></b></div>new Sanitizer({allowElements: [ "b" ]}).sanitizeFor("div", str)
// <div>hello <b>there</b></div>new Sanitizer({blockElements: [ "b" ]}).sanitizeFor("div", str)
// <div>hello <i>there</i></div>new Sanitizer({allowElements: []}).sanitizeFor("div", str)
// <div>hello there</div>
使用?allowAttributes?和?dropAttributes?參數可以定義允許或刪除哪個屬性。
const str = `<span id=foo class=bar style="color: red">hello there</span>`new Sanitizer().sanitizeFor("div", str)
// <div><span id="foo" class="bar" style="color: red">hello there</span></div>new Sanitizer({allowAttributes: {"style": ["span"]}}).sanitizeFor("div", str)
// <div><span style="color: red">hello there</span></div>new Sanitizer({dropAttributes: {"id": ["span"]}}).sanitizeFor("div", str)
// <div><span class="bar" style="color: red">hello there</span></div>
AllowCustomElements?參數允許或拒絕使用自定義元素。
const str = `<elem>hello there</elem>`new Sanitizer().sanitizeFor("div", str);
// <div></div>new Sanitizer({ allowCustomElements: true,allowElements: ["div", "elem"]}).sanitizeFor("div", str);
// <div><elem>hello there</elem></div>
注意:如果創建的?Sanitizer 沒有任何參數且沒有明確定義的配置,則將應用默認配置值。
瀏覽器支持
目前,瀏覽器對 Sanitizer API 的支持有限,并且規范仍在制定中。該 API 仍處于實驗階段,因此在生產中使用之前應關注其變化進展。
三、第三方庫
到這里我們就知道了,原生 API 和常用的前端框架都沒有提供可用的方式來安全的渲染HTML。在實際的開發中,我們可以借助已有的第三方庫來安全的渲染 HTML,下面就來介紹幾個常用給的庫。
DOMPurify
DOMPurify 是一款流行的JavaScript庫,用于在瀏覽器環境下進行HTML凈化和防止跨站腳本攻擊(XSS)。它通過移除惡意代碼、過濾危險標簽和屬性等方式來保護網頁免受XSS攻擊的威脅。DOMPurify使用了嚴格的解析和驗證策略,并提供了可配置的選項,以便開發人員根據自己的需求進行定制。它可以輕松地集成到現有的Web應用程序中,并且被廣泛認為是一種安全可靠的HTML凈化解決方案。
可以通過以下步驟來使用 DOMPurify:
(1)首先,安裝DOMPurify庫。可以通過運行以下命令來安裝它:2
npm install dompurify
(2)在需要使用的組件文件中,引入DOMPurify庫:
import DOMPurify from 'dompurify';
(3)在組件的適當位置,使用 DOMPurify 來凈化HTML字符串,下面以 React 為例:
import React from 'react';const MyComponent = () => {const userInput = '<script>alert("XSS");</script><p>Hello, World!</p>';const cleanedHtml = DOMPurify.sanitize(userInput);return <div dangerouslySetInnerHTML={{ __html: cleanedHtml }}></div>;
};
這里通過在React組件的dangerouslySetInnerHTML屬性中傳遞凈化后的HTML內容來顯示安全的HTML。
DOMPurify提供了一些選項和配置,可以使用這些選項來自定義DOMPurify的行為:
import DOMPurify from 'dompurify';// 創建自定義的白名單(允許的標簽和屬性)
const myCustomWhiteList = DOMPurify.sanitize.defaults.allowedTags.concat(['custom-tag']);
const myCustomAttributes = ['data-custom-attr'];// 創建自定義選項
const myOptions = {ALLOWED_TAGS: myCustomWhiteList,ATTRIBUTES: {...DOMPurify.sanitize.defaults.ALLOWED_ATTR,'custom-tag': myCustomAttributes,},
};const userInput = '<script>alert("XSS");</script><p>Hello, World!</p><custom-tag data-custom-attr="custom-value">Custom Content</custom-tag>';const cleanedHtml = DOMPurify.sanitize(userInput, myOptions);console.log(cleanedHtml);
// 輸出: <p>Hello, World!</p><custom-tag data-custom-attr="custom-value">Custom Content</custom-tag>
這里定義了一個自定義的白名單myCustomWhiteList,包含了DOMPurify默認的允許標簽,并添加了一個名為custom-tag的自定義標簽。我們還定義了一個包含自定義屬性data-custom-attr的對象myCustomAttributes。然后,創建了一個自定義選項myOptions,通過覆蓋ALLOWED_TAGS和ATTRIBUTES來應用自定義的白名單和屬性規則。最后,使用DOMPurify.sanitize()方法,并傳入用戶輸入的HTML和自定義選項myOptions,DOMPurify 會根據自定義規則進行過濾和凈化。
可以根據需要定義自己的白名單(允許的標簽)和屬性,并在自定義選項中使用它們來自定義DOMPurify的行為。
js-xss
js-xss是一個JavaScript庫,用于防御和過濾跨站腳本攻擊(XSS)。它提供了一組方法和函數,可以凈化和轉義用戶輸入的HTML內容,以確保在瀏覽器環境中呈現的HTML是安全的。
js-xss庫使用白名單過濾器的概念來防御XSS攻擊。它定義了一組允許的HTML標簽和屬性,同時還提供了一些選項和配置來定制過濾規則。使用js-xss,可以對用戶提交的HTML內容進行凈化,刪除或轉義所有潛在的危險代碼,只保留安全的HTML標簽和屬性。
可以通過以下步驟來使用 js-xss:
(1)安裝js-xss庫:通過npm或yarn安裝js-xss庫。
npm install xss
(2)導入js-xss庫:在React組件文件中導入js-xss庫。
import xss from 'xss';
(3)使用js-xss過濾HTML內容:在需要過濾HTML的地方,調用js-xss的方法來凈化HTML。
import React from 'react';
import xss from 'xss';const MyComponent = () => {const userInput = '<script>alert("XSS");</script><p>Hello, World!</p>';const cleanedHtml = xss(userInput);return <div dangerouslySetInnerHTML={{ __html: cleanedHtml }} />;
};export default MyComponent;
這里在MyComponent組件中使用了dangerouslySetInnerHTML屬性來渲染HTML內容。通過調用xss()函數并傳入用戶輸入的HTML,我們可以將其過濾和凈化,并將結果設置為組件的內容。
js-xss庫提供了一些選項和配置,可以使用這些選項來定義自定義的過濾規則:
import xss from 'xss';// 創建自定義WhiteList過濾規則
const myCustomWhiteList = {a: ['href', 'title', 'target'], // 只允許'a'標簽的'href', 'title', 'target'屬性p: [], // 允許空白的'p'標簽img: ['src', 'alt'], // 只允許'img'標簽的'src', 'alt'屬性
};// 創建自定義選項
const myOptions = {whiteList: myCustomWhiteList, // 使用自定義的WhiteList過濾規則
};const userInput = '<script>alert("XSS");</script><p>Hello, World!</p><a href="https://example.com" target="_blank">Example</a>';const cleanedHtml = xss(userInput, myOptions);console.log(cleanedHtml);
// 輸出: <p>Hello, World!</p><a href="https://example.com" target="_blank">Example</a>
這里定義了一個自定義的WhiteList過濾規則myCustomWhiteList,并將其傳遞給定義的選項myOptions。然后,調用xss()函數時傳入用戶輸入的HTML和自定義選項,js-xss庫會根據自定義的規則進行過濾和凈化。
sanitize-html
sanitize-html 是一個用于凈化和過濾HTML代碼的JavaScript庫。它被設計用于去除潛在的惡意或不安全的內容,以及保護應用程序免受跨站腳本攻擊(XSS)等安全漏洞的影響。它提供了一種簡單而靈活的方式來清理用戶輸入的HTML代碼,以確保只有安全的標簽、屬性和樣式保留下來,并且不包含任何惡意代碼或潛在的危險內容。
sanitize-html使用一個白名單(配置選項)來定義允許的標簽、屬性和樣式,并將所有不在白名單內的內容進行過濾和刪除。它還可以處理不匹配的標簽、標簽嵌套問題和其他HTML相關的問題。
可以通過以下步驟來使用 sanitize-html:
(1)在項目中安裝sanitize-html庫:
npm install sanitize-html
(2)在組件中引入sanitize-html庫:
import sanitizeHtml from 'sanitize-html';
(3)在組件中使用sanitizeHtml函數來凈化和過濾HTML代碼。例如,您以將用戶輸入的HTML存儲在組件的狀態或屬性中,并在渲染時應用sanitizeHtml函數:
import React from 'react';
import sanitizeHtml from 'sanitize-html';function MyComponent() {const userInput = '<script>alert("XSS");</script><p>Hello, World!</p>';const cleanedHtml = sanitizeHtml(userInput);return (<div><div dangerouslySetInnerHTML={{ __html: cleanedHtml }}></div></div>);
}
這里在組件內部定義了用戶輸入的HTML代碼,并使用sanitizeHtml函數對其進行凈化。然后,使用dangerouslySetInnerHTML屬性將經過凈化的HTML代碼渲染到頁面上。
可以使用sanitize-html提供的sanitize函數并傳遞一個配置對象作為參數來自定義sanitize-html的配置,配置對象可以包含一系列選項,用于定義過濾規則和允許的HTML標簽和屬性等。
import sanitizeHtml from 'sanitize-html';const customConfig = {allowedTags: ['b', 'i', 'u'], // 允許的標簽allowedAttributes: {a: ['href'] // 允許的a標簽屬性},allowedSchemes: ['http', 'https'], // 允許的URL協議allowedClasses: {b: ['bold', 'highlight'], // 允許的b標簽的classi: ['italic'] // 允許的i標簽的class},transformTags: {b: 'strong', // 將b標簽轉換為strong標簽i: 'em' // 將i標簽轉換為em標簽},nonTextTags: ['style', 'script', 'textarea', 'noscript'] // 不允許解析的標簽
};const userInput = '<b class="bold">Hello</b> <i class="italic">World</i> <a href="https://example.com">Link</a>';const cleanedHtml = sanitizeHtml(userInput, customConfig);
這里創建了一個名為customConfig的配置對象,其中包含了一些自定義的過濾規則和選項。這個配置對象定義了允許的標簽、允許的屬性、允許的URL協議、允許的CSS類名、標簽的轉換規則以及不允許解析的標簽等。
然后,將用戶輸入的HTML代碼作為第一個參數傳遞給sanitizeHtml函數,并將customConfig作為第二個參數傳遞。sanitizeHtml函數將根據配置對象中定義的規則對HTML代碼進行過濾和凈化,并返回經過凈化后的HTML代碼。
相關拓展:前端開發利器
扯個嗓子!關于目前低代碼在技術領域很活躍!
低代碼是什么?一組數字技術工具平臺,能基于圖形化拖拽、參數化配置等更為高效的方式,實現快速構建、數據編排、連接生態、中臺服務等。通過少量代碼或不用代碼實現數字化轉型中的場景應用創新。它能緩解甚至解決龐大的市場需求與傳統的開發生產力引發的供需關系矛盾問題,是數字化轉型過程中降本增效趨勢下的產物。
這邊介紹一款好用的低代碼平臺——JNPF快速開發平臺。近年在市場表現和產品競爭力方面表現較為突出,采用的是最新主流前后分離框架(SpringBoot+Mybatis-plus+Ant-Design+Vue3)。代碼生成器依賴性低,靈活的擴展能力,可靈活實現二次開發。
以JNPF為代表的企業級低代碼平臺為了支撐更高技術要求的應用開發,從數據庫建模、Web API構建到頁面設計,與傳統軟件開發幾乎沒有差異,只是通過低代碼可視化模式,減少了構建“增刪改查”功能的重復勞動,還沒有了解過低代碼的伙伴可以嘗試了解一下。
應用:https://www.jnpfsoft.com/?csdn
有了它,開發人員在開發過程中就可以輕松上手,充分利用傳統開發模式下積累的經驗。所以低代碼平臺對于程序員來說,有著很大幫助。