[APACHE DOCUMENTATION]

跨站脚本攻击信息:编码示例

简介

我们相信您已经熟悉跨站脚本攻击的安全问题以及其工作原理。如果您不熟悉,请在继续之前查看有关此问题的CERT 咨询 CA-2000-02,以获取详细信息。

本文档重点介绍如何在将数据输出到客户端之前安全地对其进行编码。主要方法是通过实体编码,如 CERT 咨询中所述,使用诸如 "<" 之类的实体。

关于编码的一般评论

请注意,通常,许多执行实体编码的函数以仅适用于属性值之外、在普通块级元素(如文本段落)中使用的方式进行编码。下面引用的许多函数都属于此类。这意味着它们可能不会对诸如双引号或单引号之类的字符进行编码。如果您没有对来自用户输入的属性值使用引号,那么您需要对更多字符进行编码。始终使用引号,您就不必担心这个问题。

不幸的是,在属性值或脚本主体(例如,在 "<SCRIPT>" 标签内)中对数据进行编码的情况更加复杂,也鲜为人知。如果您遇到这种情况,您可能需要考虑过滤特殊字符(如CERT 技术提示中所述),而不是对其进行编码。通常,建议使用编码,因为它不需要您对哪些字符可以合法输入并需要传递进行决策,并且对现有功能的影响较小。

在属性值中安全地对数据进行编码之所以困难,是因为某些不被视为特殊字符的字符可以排列成在某些属性值中产生意外效果。这与属性关联的标签以及客户端对其的解释方式密切相关。例如,如果您允许用户输入 HREF 属性的值,并且您对其进行了正确编码,您最终可能会输出一个类似于以下的标签

<A HREF="javascript:document.writeln(document.cookie + &quot;&lt;BR&gt;&quot;)">
即使您已对特殊字符进行了正确编码,许多流行的浏览器仍会将 "javascript:" URL 解释为包含要在当前文档上下文中执行的 JavaScript。

尚未解决的问题之一是,究竟哪些 HTML 标签是 "安全的",允许通过,以及执行此操作的算法是什么样的。许多网站希望允许用户输入有限的 "安全" HTML 子集。这仍然是一个悬而未决的问题。这个问题已经存在很长时间了,我们希望跨站脚本攻击问题能够促使人们更多地关注解决这个问题。

如果您要对用户输入的数据进行 URL 编码,那么 URL 编码(也称为百分比编码)是合适的。不幸的是,这可能是一件很复杂的事情,因为 "http://" 中的特殊字符(例如)必须保持未编码,因为它们是 URL 语法的组成部分。需要更好的解决方案来处理这个问题。

另请注意,出于历史原因,某些 URL 编码函数将空格编码为 "+”。这仅适用于 CGI 的查询字符串,并且不会在 URL 的其他部分中正确地对空格进行编码。

我们意识到,所有这些特殊情况以及缺乏对用户数据进行编码的单一防弹步骤集(无论它出现在页面上的哪个位置),在某些情况下都会使解决此问题的任务非常具有挑战性。我们希望我们有更好的答案,并且正在努力填补模糊的领域。

PHP 示例

<?
$Text = "foo<b>bar";
$URL = "foo<b>bar.html";
echo HTMLSpecialChars($Text), "<BR>";     
echo "<A HREF=\"", rawurlencode($URL), "\">link</A>";
?>

请注意,PHP 还具有一个 strip_tags() 函数,该函数将从字符串中删除所有 HTML 标签。在以下方式使用此函数

	echo strip_tags($Text);
将从输入中删除所有 HTML。但是,如果您以以下形式使用它
	echo strip_tags($Text, "<B>");
仅允许 "<B>" 标签通过,您仍然容易受到用户插入脚本代码的攻击。根据设计,此函数不会从标签中删除属性。这意味着通常可以包含诸如 JavaScript 事件属性之类的东西。以下是一个由上述 strip_tags() 调用允许的标签示例
	<B onmouseover="document.location='http://www.cert.org/'">

某些客户端接受对其他方面无害的标签上的此类属性。

Apache 模块示例

char *Text = "foo<b>bar";
char *URL = "foo<b>bar.html";
ap_rvputs(r, ap_escape_html(r->pool, Text), "<BR>", NULL);
ap_rvputs(r, "<A HREF=\"", ap_escape_uri(r->pool, URL), "\">link</A>", NULL);

mod_perl 示例

$Text = "foo<b>bar";
$URL = "foo<b>bar.html";
$r->print(Apache::Util::escape_html($Text), "<BR>");
$r->print("<A HREF=\"", Apache::Util::escape_uri($URL), "\">link</A>");

这使用与 Apache 模块示例中相同的函数,从 Perl 而不是直接从 C 调用。

Perl 示例

use CGI ();
$Text = "foo<b>bar";
$URL = "foo<b>bar.html";
print CGI::escapeHTML($Text), "<BR>";
print qq(<A HREF="), CGI::escape($URL), qq(">link</A>);

请注意,如果您以其全部预期作用使用 CGI.pm 模块,而不是仅仅使用其中的辅助函数,它将在许多地方自动对特殊字符进行编码。不幸的是,这在所有情况下可能仍然不够。有关此模块功能的更多详细信息,请参阅 http://stein.cshl.org/WWW/software/CGI/ 上的文档。