跳到內容

HTML 消毒器

編輯此頁面

HTML 消毒器組件旨在將不受信任的 HTML 程式碼(例如,由瀏覽器中的 WYSIWYG 編輯器建立)消毒/清理為可信任的 HTML。它基於 HTML Sanitizer W3C 標準提案

HTML 消毒器從頭開始建立新的 HTML 結構,僅採用組態允許的元素和屬性。這表示傳回的 HTML 非常可預測(僅包含允許的元素),但對於格式錯誤的輸入(例如,無效的 HTML)效果不佳。消毒器的目標是兩種使用案例

  • 防止基於 XSS 或其他依賴在訪客瀏覽器上執行惡意程式碼的技術的安全攻擊;
  • 產生始終遵循特定格式(僅限特定標籤、屬性、主機等)的 HTML,以便能夠使用 CSS 持續設定結果輸出的樣式。這也可以保護您的應用程式免受與例如變更整個頁面的 CSS 相關的攻擊。

安裝

您可以使用以下方式安裝 HTML 消毒器組件

1
$ composer require symfony/html-sanitizer

基本用法

使用 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

除了允許/封鎖元素和屬性之外,您還可以控制 <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 消毒器會檢查以下屬性:srchreflowsrcbackgroundping

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
本作品,包括程式碼範例,根據 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本