HTML 消毒器
HTML 消毒器組件旨在將不受信任的 HTML 程式碼(例如,由瀏覽器中的 WYSIWYG 編輯器建立)消毒/清理為可信任的 HTML。它基於 HTML Sanitizer W3C 標準提案。
HTML 消毒器從頭開始建立新的 HTML 結構,僅採用組態允許的元素和屬性。這表示傳回的 HTML 非常可預測(僅包含允許的元素),但對於格式錯誤的輸入(例如,無效的 HTML)效果不佳。消毒器的目標是兩種使用案例
- 防止基於 XSS 或其他依賴在訪客瀏覽器上執行惡意程式碼的技術的安全攻擊;
- 產生始終遵循特定格式(僅限特定標籤、屬性、主機等)的 HTML,以便能夠使用 CSS 持續設定結果輸出的樣式。這也可以保護您的應用程式免受與例如變更整個頁面的 CSS 相關的攻擊。
基本用法
使用 HtmlSanitizer 類別來消毒 HTML。在 Symfony 框架中,此類別作為 html_sanitizer
服務提供。當為 HtmlSanitizerInterface 類型提示時,此服務將自動 自動裝配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// src/Controller/BlogPostController.php
namespace App\Controller;
// ...
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
class BlogPostController extends AbstractController
{
public function createAction(HtmlSanitizerInterface $htmlSanitizer, Request $request): Response
{
$unsafeContents = $request->getPayload()->get('post_contents');
$safeContents = $htmlSanitizer->sanitize($unsafeContents);
// ... proceed using the safe HTML
}
}
注意
HTML 消毒器的預設設定允許所有「安全」元素和屬性,如 W3C 標準提案 所定義。實際上,這表示結果程式碼將不包含任何腳本、樣式或其他可能導致網站行為或外觀不同的元素。稍後在本文中,您將學習如何 完全自訂 HTML 消毒器。
為特定情境消毒 HTML
預設的 sanitize() 方法會清除 HTML 程式碼,以用於 <body>
元素中。使用 sanitizeFor() 方法,您可以指示 HTML 消毒器針對 <head>
或更特定的 HTML 標籤自訂此設定
1 2 3 4 5 6 7 8 9 10
// tags not allowed in <head> will be removed
$safeInput = $htmlSanitizer->sanitizeFor('head', $userInput);
// encodes the returned HTML using HTML entities
$safeInput = $htmlSanitizer->sanitizeFor('title', $userInput);
$safeInput = $htmlSanitizer->sanitizeFor('textarea', $userInput);
// uses the <body> context, removing tags only allowed in <head>
$safeInput = $htmlSanitizer->sanitizeFor('body', $userInput);
$safeInput = $htmlSanitizer->sanitizeFor('section', $userInput);
從表單輸入消毒 HTML
HTML 消毒器組件直接與 Symfony 表單整合,以在您的應用程式處理之前消毒表單輸入。
您可以使用 sanitize_html
選項在 TextType
表單或任何擴充此類型的表單(例如 TextareaType
)中啟用消毒器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Form/BlogPostType.php
namespace App\Form;
// ...
class BlogPostType extends AbstractType
{
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'sanitize_html' => true,
// use the "sanitizer" option to use a custom sanitizer (see below)
//'sanitizer' => 'app.post_sanitizer',
]);
}
}
在 Twig 模板中消毒 HTML
除了消毒使用者輸入之外,您還可以使用 sanitize_html()
篩選器在 Twig 模板中輸出 HTML 程式碼之前對其進行消毒
1 2 3 4
{{ post.body|sanitize_html }}
{# you can also use a custom sanitizer (see below) #}
{{ post.body|sanitize_html('app.post_sanitizer') }}
設定
HTML 消毒器的行為可以完全自訂。這可讓您明確聲明允許哪些元素、屬性甚至屬性值。
您可以透過在設定中定義新的 HTML 消毒器來執行此操作
1 2 3 4 5 6 7
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
block_elements:
- h1
此設定定義了新的 html_sanitizer.sanitizer.app.post_sanitizer
服務。對於具有 HtmlSanitizerInterface $appPostSanitizer
參數的服務,此服務將 自動裝配。
允許元素基準
您可以透過使用兩個基準之一來啟動自訂 HTML 消毒器
- 靜態元素
- 來自 W3C 標準提案 的基準允許清單上的所有元素和屬性(不包括腳本)。
- 安全元素
- 來自「靜態元素」清單的所有元素和屬性,不包括也可能導致 CSS 注入/點擊劫持的元素和屬性。
1 2 3 4 5 6 7 8
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# enable either of these
allow_safe_elements: true
allow_static_elements: true
允許元素
這會將元素新增至允許清單。對於每個元素,您還可以指定該元素上允許的屬性。如果未給定,則允許 W3C 標準提案 中的所有允許屬性。
1 2 3 4 5 6 7 8 9 10 11 12 13
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
allow_elements:
# allow the <article> element and 2 attributes
article: ['class', 'data-attr']
# allow the <img> element and preserve the src attribute
img: 'src'
# allow the <h1> element with all safe attributes
h1: '*'
封鎖和捨棄元素
您也可以封鎖(元素將被移除,但其子項將被保留)或捨棄(元素及其子項將被移除)元素。
這也可以用於從允許清單中移除元素。
1 2 3 4 5 6 7 8 9 10 11
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
# remove <div>, but process the children
block_elements: ['div']
# remove <figure> and its children
drop_elements: ['figure']
允許屬性
使用此選項,您可以指定將在傳回的 HTML 中保留哪些屬性。該屬性將在給定的元素上或在此設定之前允許的所有元素上允許。
1 2 3 4 5 6 7 8 9 10 11 12
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
allow_attributes:
# allow "src' on <iframe> elements
src: ['iframe']
# allow "data-attr" on all elements currently allowed
data-attr: '*'
捨棄屬性
此選項可讓您取消允許先前允許的屬性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
allow_attributes:
# allow the "data-attr" on all safe elements...
data-attr: '*'
drop_attributes:
# ...except for the <section> element
data-attr: ['section']
# disallows "style' on any allowed element
style: '*'
強制屬性值
使用此選項,您可以在元素上強制具有給定值的屬性。例如,使用以下設定始終在每個 <a>
元素上設定 rel="noopener noreferrer"
(即使原始元素不包含 rel
屬性)
1 2 3 4 5 6 7 8 9
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
force_attributes:
a:
rel: noopener noreferrer
強制/允許連結 URL
除了允許/封鎖元素和屬性之外,您還可以控制 <a>
元素的 URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
# if `true`, all URLs using the `http://` scheme will be converted to
# use the `https://` scheme instead. `http` still needs to be allowed
# in `allowed_link_schemes`
force_https_urls: true
# specifies the allowed URL schemes. If the URL has a different scheme, the
# attribute will be dropped
allowed_link_schemes: ['http', 'https', 'mailto']
# specifies the allowed hosts, the attribute will be dropped if the
# URL contains a different host. Subdomains are allowed: e.g. the following
# config would also allow 'www.symfony.com', 'live.symfony.com', etc.
allowed_link_hosts: ['symfony.com']
# whether to allow relative links (i.e. URLs without scheme and host)
allow_relative_links: true
強制/允許媒體 URL
與 連結 URL 類似,您也可以控制 HTML 中其他媒體的 URL。HTML 消毒器會檢查以下屬性:src
、href
、lowsrc
、background
和 ping
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
# if `true`, all URLs using the `http://` scheme will be converted to
# use the `https://` scheme instead. `http` still needs to be allowed
# in `allowed_media_schemes`
force_https_urls: true
# specifies the allowed URL schemes. If the URL has a different scheme, the
# attribute will be dropped
allowed_media_schemes: ['http', 'https', 'mailto']
# specifies the allowed hosts, the attribute will be dropped if the URL
# contains a different host which is not a subdomain of the allowed host
allowed_media_hosts: ['symfony.com'] # Also allows any subdomain (i.e. www.symfony.com)
# whether to allow relative URLs (i.e. URLs without scheme and host)
allow_relative_medias: true
最大輸入長度
為了防止 阻斷服務攻擊,預設情況下,HTML 消毒器將輸入長度限制為 20000
個字元(由 strlen($input)
測量)。所有超過該長度的內容都將被截斷。使用此選項可增加或減少此限制
1 2 3 4 5 6 7 8 9
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
# inputs longer (in characters) than this value will be truncated
max_input_length: 30000 # default: 20000
可以透過將最大輸入長度設定為 -1
來停用此長度限制。請注意,這可能會使您的應用程式暴露於 阻斷服務攻擊。
自訂屬性消毒器
連結和媒體 URL 的控制由 UrlAttributeSanitizer 完成。您也可以實作自己的屬性消毒器,以控制 HTML 中其他屬性的值。建立一個實作 AttributeSanitizerInterface 的類別,並將其註冊為服務。在此之後,使用 with_attribute_sanitizers
為 HTML 消毒器啟用它
1 2 3 4 5 6 7 8 9 10 11 12
# config/packages/html_sanitizer.yaml
framework:
html_sanitizer:
sanitizers:
app.post_sanitizer:
# ...
with_attribute_sanitizers:
- App\Sanitizer\CustomAttributeSanitizer
# you can also disable previously enabled custom attribute sanitizers
#without_attribute_sanitizers:
# - App\Sanitizer\CustomAttributeSanitizer