9.5. 控制输出中的字符编码

一般来说,安全程序必须确保其客户端与其所做的任何假设同步。Web 应用程序经常遇到的一个问题是它们忘记指定其输出的字符编码。如果所有数据都来自可信来源,这不是问题,但如果某些数据来自不可信来源,则不可信来源可能会偷偷插入使用与安全程序预期编码不同的数据。这为跨站恶意内容攻击打开了大门;有关更多信息,请参阅第 5.10 节

CERT 关于恶意代码缓解的技术提示很好地解释了未指定字符编码的问题,所以我在此引用它:

许多网页未定义字符编码(HTTP 中的“charset”参数)。在早期版本的 HTML 和 HTTP 中,如果未定义字符编码,则应默认为 ISO-8859-1。事实上,许多浏览器都有不同的默认值,因此不可能依赖默认为 ISO-8859-1。HTML 版本 4 使其合法化 - 如果未指定字符编码,则可以使用任何字符编码。

如果 Web 服务器未指定正在使用的字符编码,则它无法知道哪些字符是特殊的。具有未指定字符编码的网页在大多数情况下都可以工作,因为大多数字符集将相同的字符分配给 128 以下的字节值。但是,128 以上的哪些值是特殊的呢?一些 16 位字符编码方案对于特殊字符(如“<”)具有额外的多字节表示形式。一些浏览器识别这种替代编码并对其进行操作。这是“正确”的行为,但这使得使用恶意脚本的攻击更难防范。服务器根本不知道哪些字节序列代表特殊字符。

例如,UTF-7 为“<”和“>”提供了替代编码,并且一些流行的浏览器将这些识别为标签的开始和结束。这不是这些浏览器中的错误。如果字符编码真的是 UTF-7,那么这是正确的行为。问题在于,可能会出现浏览器和服务器在编码上意见不一致的情况。

值得庆幸的是,虽然解释这个问题很棘手,但它在 HTML 中的解决方案很简单。在 HTML 标头中,只需指定字符集,就像 CERT 的这个示例一样
<HTML>
<HEAD>
<META http-equiv="Content-Type"
content="text/html; charset=ISO-8859-1">
<TITLE>HTML SAMPLE</TITLE>
</HEAD>
<BODY>
<P>This is a sample HTML page
</BODY>
</HTML>

从技术角度来看,更好的方法是将字符编码设置为 HTTP 协议输出的一部分,尽管某些库使这更加困难。从技术上讲,这更好,因为它不会强制客户端检查标头以确定使其能够读取标头中 META 信息的字符编码。当然,在实践中,无法读取上面给出的 META 信息并正确使用它的浏览器不会在市场上获得成功,但这又是另一个问题。无论如何,这仅意味着服务器需要作为 HTTP 协议的一部分发送带有期望值的“charset”。不幸的是,很难衷心推荐这种(技术上更好的)方法,因为一些较旧的 HTTP/1.0 客户端无法正确处理显式的 charset 参数。尽管 HTTP/1.1 规范要求客户端遵守该参数,但这仍然足够令人怀疑,您可能应该将其用作强制使用正确字符编码的辅助手段,而不是您唯一的机制。