HTML Sanitizer
HTML Sanitizer 组件旨在将不受信任的 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 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
强制/允许链接 URLs
除了允许/阻止元素和属性之外,您还可以控制 <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 清理器检查以下属性: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
最大输入长度
为了防止 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