humanmade/clean-html

WordPress 中的安全 HTML。添加类似 esc_*() 的函数以允许一些 HTML。

2.0.0 2020-06-09 13:13 UTC

This package is auto-updated.

Last update: 2024-09-09 22:26:11 UTC


README

当需要允许一些 HTML 时,引入类似 esc_*() 的函数。

理由

背景

在处理任何类型的数据时,最佳实践是在尽可能晚的时候对输出进行转义。值通常不知道它们将要去哪里使用,所以你需要基于你输出的上下文进行转义。这些就像 esc_attr() 用于 HTML 属性值,esc_html() 用于 HTML 中的文本等。

即使值能够了解它们的输出上下文,如果转义不当,用户仍然可能构建恶意输出。因此,你需要对输入进行清理(以确保值是正确的),以及在输出时进行转义(以确保值被正确地输出到上下文中)。

目前,每个 WordPress 网站都存在一个明显的转义漏洞,因此也存在安全漏洞。

在翻译 WordPress 中的字符串时,最常用的函数是 __()(翻译并返回)或 _e()(翻译并输出)。尽可能使用这些函数时也需要进行转义,以确保翻译不会意外地破坏你的输出。因此,提供了 esc_html_e()esc_attr_e() 等便利函数。

然而,当你需要在翻译中包含 HTML 时,这就会失效。翻译最佳实践表示,在翻译字符串时,应尽可能为翻译者提供尽可能多的信息。这意味着在字符串中包含 HTML 标签,以便翻译者可以理解句子是如何构成的。

使用占位符等“聪明”的技巧可以解决这个问题,例如

$text = sprintf(
	esc_html__( 'This is some text %1$swith a link%2$s'),
	'<a href="http://example.com/">',
	'</a>'
);

但是请注意,这对翻译者来说更难理解,因为他们无法直观地了解情况而不检查代码。即使有翻译者注释,仍然很难理解。此外,也没有保证这是安全的。你可以交换占位符,或者遗漏部分。最佳实践表明,我们应采取以下做法

$text = sprintf(
	esc_html__( 'This is some text <a href="%1$s">with a link</a>'),
	'http://example.com/'
);

目前,政策基本上是将带有 HTML 的翻译字符串视为可信的。这不仅将负担推给了 GlotPress 中的翻译验证器,而且意味着你不再控制你的输出。这是一个等待被利用的攻击向量。

我们该如何解决这个问题呢?

WordPress 包含专门设计来帮助解决这个问题功能的函数。毕竟,人们可以提交包含 HTML 的评论或帖子,但 WP 可以很好地处理这个问题。WordPress 通过一个名为 kses 的库来处理这个问题,该库将 HTML 清理为一小部分安全的 HTML 子集。帖子可以包含比评论更多的 HTML 标签,因为它们通常是半可信用户。

kses 很好,但通常不用于大型 HTML 块之外,如帖子或评论内容。原因通常被解释为性能。众所周知,kesp 很慢,因为它必须基本上拆解 HTML,然后使用允许的标签重建它。

然而,Zack Tollman写了一篇精彩的帖子,质疑了关于kses性能的这种公认知识。Zack的研究表明,虽然kses在处理较长的内容(如帖子内容)时性能较差,但实际上它在处理短字符串时与其它转义功能的表现相近。当将允许的元素从默认值减少到仅需要的元素时,这一点更加明显。

clean_html

这个库提供了一个简单、高效的方式来对翻译字符串进行清理。它不像kses那样要求你处理其内部结构,而是更接近于esc_html等函数。

如果安全措施不能被有效使用,那么它就毫无价值。大部分情况下,clean_html可以使用与开发者习惯使用其他转义函数相同的方式进行使用。

以下是一个快速示例,演示其简便性

<!-- Previously -->
<p><?php _e( 'This is a terrific use of <code>WP_Error</code>.' ) ?></p>

<!-- Secure version -->
<p><?php print_clean_html( __( 'This is a terrific use of <code>WP_Error</code>.' ), 'code' ) ?></p>

即使一个恶意翻译者将其修改为包含指向垃圾网站的链接(或者更糟),这也会被clean_html捕获并删除。

以我们上面的原始示例为例,我们可以修改它,只允许a标签

$text = clean_html(
	sprintf(
		__( 'This is some text <a href="%1$s">with a link</a>'),
		'http://example.com/'
	),
	'a'
);

就这么简单。你还可以使用逗号分隔的字符串或元素列表使用多个元素。

$text = clean_html(
	sprintf(
		__( 'This is <code>some</code> text <a href="%1$s">with a link</a>'),
		'http://example.com/'
	),
	'a, code' // or array( 'a', 'code' )
);

如果你需要自定义属性,可以使用kses风格的属性指定符。它们也可以混合使用。

$text = clean_html(
	sprintf(
		__( 'This is <span class="x">some</span> text <a href="%1$s">with a link</a>'),
		'http://example.com/'
	),
	array(
		'a',
		'span' => array(
			'class' => true,
		),
	)
);

性能测试

在快速测试中,字符串'hello with a <a href="wak://example.com">malicious extra link!<///q><o>b'被通过了带有aclean_htmlesc_html各10,000次迭代。虽然这两个函数执行的任务不同,但它们都是转义函数,所以比较它们的性能有助于了解这种方法是否可以在生产代码中使用。

在非科学的试验中,这给出了clean_html为0.96秒,esc_html为1.07秒的数值,每个函数都进行了10,000次试验。这表明clean_html至少与其它转义函数在性能上是相当的。

使用这个库

使用这个库有两个步骤

  1. 将这个库作为git子模块添加。
  2. 在使用之前加载clean-html.php。我们建议在mu-plugins中加载,但如果你希望更早地加载它,也可以通过wp-config.php进行加载。

完成。开始使用这个函数。