跳到内容

HTML Sanitizer

编辑此页

HTML Sanitizer 组件旨在将不受信任的 HTML 代码(例如,在浏览器中由 WYSIWYG 编辑器创建的代码)清理/净化为可信任的 HTML。它基于 HTML Sanitizer W3C 标准提案

HTML 清理器从头开始创建一个新的 HTML 结构,仅采用配置允许的元素和属性。这意味着返回的 HTML 非常可预测(它只包含允许的元素),但它不能很好地处理格式错误的输入(例如,无效的 HTML)。清理器的目标是两种用例

  • 防止基于 XSS 或其他依赖于在访问者浏览器上执行恶意代码的技术的安全攻击;
  • 生成始终遵循特定格式(仅限特定标签、属性、主机等)的 HTML,以便能够使用 CSS 一致地设置结果输出的样式。 这还可以保护您的应用程序免受与例如更改整个页面的 CSS 相关的攻击。

安装

您可以使用以下命令安装 HTML Sanitizer 组件

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 Sanitizer 组件直接与 Symfony Forms 集成,以在表单输入被应用程序处理之前对其进行清理。

您可以在 TextType 表单或任何扩展此类型的表单(例如 TextareaType)中使用 sanitize_html 选项启用清理器

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

除了清理用户输入之外,您还可以在 Twig 模板中输出 HTML 代码之前使用 sanitize_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

强制/允许媒体 URLs

链接 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

最大输入长度

为了防止 DoS 攻击,默认情况下,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 来禁用此长度限制。 请注意,这可能会使您的应用程序暴露于 DoS 攻击

自定义属性清理器

链接和媒体 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 许可获得许可。
目录
    版本