6.5 如何正确处理字符编码
要想正确地处理字符编码问题,需要遵循下面 4 条要求。
- 在应用内使用统一的字符集
- 输入非法数据时报错并终止处理
- 处理数据时使用正确的编码方式
- 输出时设置正确的字符编码方式
如果把这 4 项整理到一起的话,如图 6-20 所示。
图 6-20 正确处理字符编码的要点
下面开始分别对这几项进行详细说明。
- 在应用内使用统一的字符集
即使抛开安全性来说,也应该在全系统内统一所使用的字符集。如果系统中存在不同的字符集,且要转换的目标字符集不存在原字符集的字符的话,就会出现乱码问题。
现在的 OS、编程语言、数据库等系统软件都能够支持 Unicode,所以可以说如果 Web 应用也都统一使用 Unicode 的话将是最安全的。
- 不能统一使用 Unicode 的事例
如果将应用系统的字符集统一成 Unicode 的话,那么在不支持 Unicode 的设备上就会出现问题。典型的例子有下面两个。
- 手机浏览器 16
- 电子邮件
由于大多数手机浏览器中只支持 Shift_JIS 编码方式 17 ,发送邮件的时候字符编码主流也是 ISO-2022-JP,所以这两种情况都很难统一使用 Unicode 字符编码方式。
- 面向手机 Web 应用中字符编码的处理方法
面向手机的 Web 应用程序中,典型的处理字符编码的 方法一般如图 6-21,在应用程序的外部(HTTP 消息)使用 Shfit_JIS 编码方式,在应用内部使用 UTF-8 或者 UTF-16,在输入输出时在两种编码方式之间相互转换。
图 6-21 面向手机 Web 应用的字 符编码设置
输入的时候从 Shfit_JIS 转换为 UTF-8 没什么问题。在编码转换后,会使用变换后的数据进行输入值检查、转义等处 理,假如这时候即使发生乱码的问题,之后也还会使用乱码后的数据进行转义等进行数据复原。
但是,输出时从 UTF-8 转换为 Shift_JIS 的时候,有可能出现日元符号(U+00A5)变成反斜线(0x5C)的问题。输出时进行编码转换之所以会发生问题,如图 6-21 所示,是因为在进行转义处理后字符集已经改变了,编码转换后可能出现需要进行转义而没有进行转义的字符。
但是如果原来输入的数据全是 Shfit_JIS 编码格式的话,因为不会输入 Shfit_JIS 编码里不存在的字符,所以不会发生任何问题 18 。
另一方面,如果数据库里保存的是类似 U+00A5 这样 Unicode 的固有的字符的话,可以预先进行一遍 UTF-8 → Shift_JIS → UTF-8 的编码转换过程。笔者把这种方法叫作“字符集降级处理”。通过字符集降级处理让可能出现的乱码的问题提前出现,在此基础上再进行转义等处理,就可以防止由于字符集转换导致的各种问题的发生。图 6-22 显示了这种处理的大概流程,图中的处理顺序很重要。如果在对输入进行转义处理之前不先做字符集降级处理的话,就有可能导致系统出现漏洞。
图 6-22 字符集降级处理
- 电子邮件中字符编码的处理方法
长时间以来,日语邮件中的字符编码几乎都是 ISO-2022-JP。但是最近能处理 UTF-8 编码的邮件客户端正在增加 19 。
在应用程序内部使用 Unicode 编码,在发送邮件的 时候进行 ISO-2022-JP 编码转换,如果邮件中存在 ISO-2022-JP 不支持的字符的话,就会出现乱码。但是在笔者的调查中,这并不会导致系统在安全上出现什么漏洞。其原因在于如果在邮件处理(MIME 编码等)开始之前进行 ISO-2022-JP 编码,和邮件处理中必须要注意的可能在安全上出问题的回车换行等字符,在任何字符编码里都是共通的。
所以在收发电子邮件的时候,可以认为不会发生由字符编码导致的安全漏洞。
- 不能统一使用 Unicode 的事例
- 输入非法数据时报错并终止处理
如同 4.2 节里讲到的那样,在进行输入检查的时候如果发现了输入字符中存在非法编码数据的话,就立即报错并终止继续处理是一个比较好的处理方法。我们可以认为字符编码数据的正确性是应用程序正常运行的基本前提。
在现代 Web 程序开发用的高级语言中,Java 和 ASP.NET(C# 或者 VB.NET)会对输入数据进行字符编码的变换,在这个过程中如果发现了非法的编码数据的话,会用替换字符(Replacement Character、U+FFFD)来替换非法字符。
Perl(版本 5.8 以上)的话,可以通过调用
decode
来实现类似的功能,在将数据转换为内部形式时进行非法字符编码数据的替换工作。PHP 的话则没有这种非法字符编码数据自动替换功能,但是我们可以像 4.2 节里讲的那样,可以调用
mb_check_encoding
来检查输入数据的字符编码。 - 处理数据时使用正确的编码方式
要想正确的进行字符编码处理,需要遵循下面的原则。
- 只使用对应多字节字符的函数和实现
- 在函数的参数中明确设置编码方式
下面分别说明这两个原则。
- 只使用对应多字节字符的函数和实现
为了正确地进行字符编码操作,需要使用多字节版本的实现及函数。Java、.NET、Perl(版本 5.8 或更新)的话没有特别需要注意的,但是 PHP 从语言本身的话并没有支持多字节字符,所以需要遵循下面的要求。
- 源代码需要用 UTF-8(推荐)或者 EUC-JP 的编码方式保存
- 将配置文件 php.ini 的 mbstring.internal_encoding 设置为与源代码文件的编码一致
- 原则上所有字符串操作都是用 mbstring 系列版本的函数(即使不太可能出现非英语字符的时候也一样遵循此原则)
- 在函数的参数中明确设置编码方式
这个原则主要也是在使用 PHP 时需要注意的。在调用 mbstring 系列的函数时如果不显式指定编码方式的话,会使用 php.ini 文件里设置的 mbstring.internal_encoding 值进行处理。但是像
htmlspecialchars
这样的函数则不会使用 mbstring.internal_encoding 作为默认值进行处理,需要调用的时候手工设置字符编码方式。
专栏:调用 htmlspecialchars 函数时必须指定字符编码方式
一直以来很多 PHP 的入门书籍都宣称在使用
htmlspecialchars
时可以不用指定字符编码方式,其实这种认识是错误的。老版本的htmlspecialchars
函数在字符编码方面的检查不是非常充分,这种说法只不过是一种广泛传播的误解。最新版本的htmlspecialchars
函数已经加强了字符编码方面的检查,通过指定字符编码,可以使系统的安全性更上一层。 - 输出时设置正确的字符编码方式
在下面几种情况下,需要在输出时手工进行编码方式设置。
- 正确设置 HTTP 返回头的 Content-Type 里的编码方式(请参考 4.3 节)
- 正确设置数据库的字符编码方式(请参考 4.4 节)
- 在所有需要设置字符编码方式的地方设置编码方式
下面分别对这几项进行说明。
- 正确设置 HTTP 返回头的 Content-Type 里的编码方式
虽然本书里没有涉及此部分内容,但是如果在 HTTP 返回头中的 Content-Type 里设置一个不正确的编码方式的话,就可能发生浏览器将文件内容误认为 UTF-7 编码进行解析,从而出现 XSS 漏洞的风险。这里所说的“正确设置”,是指设置一个浏览器能理解的字符编码方式的意思。如果是处理日语的话 20 ,那么编码方式应该是下面三种方式之一。注意下面的编码方式中下划线和中划线的区别。
- UTF-8(推荐)
- Shift_JIS(只用在面向手机的 Web 网站的情况下)
- EUC-JP
- 正确设置数据库的字符编码方式
数据库的字符编码方式设置也会给系统安全性带来影响。在数据库中有下面几个地方可以进行编码方式的设置(根据数据库不同设置场所也不同)。
- 保存时的编码方式(可以以列、表、数据库为单位进行设置)
- 数据库内处理时使用的编码方式
- 连接到数据库引擎时使用的编码方式
不管在哪里进行设置,都推荐统一使用 Unicode 的编码方式,即 UTF-8 或者 UTF-16。
- 通过“尾骶骨测试”来确认数据库编码方式是否正常工作 21
下面简单介绍下如何确认整个数据库是否正确的设置为了 Unicode 编码了,这就是将“尾骶骨”三个字录入数据库,然后在页面上确认这几个字是否能正常显示出来。如果“尾骶骨”能正常显示的话,那么就说明数据库中的 Unicode 设置能正常工作。如果显示出来的是“尾〓骨”或者“尾 骨”的话,那么就要怀疑是不是处理的哪一环节中出现 Shift_JIS 或者 EUC-JP 了。笔者将这种测试方法叫作“尾骶骨测试”。
“尾骶骨”中的“骶”字(U+9AB6)是 JIS X 0208 中不存在的汉字(JIS 第 3 基准汉字),所以这个词汇非常适合做数据库编码设置检查的测试用例。
- 在所有需要设置字符编码方式的地方设置编码方式
根据应用程序中所使用的编程语言或者第三方库的不同,可能在文件读写、邮件发送等时候都能够手工设置编码方式。这时候需要对所有的可以设置字符编码方式的地方都要进行确认,根据实际情况设置正确的编码方式。
- 其他对策:尽量避免编码自动检测
根据所使用编程语言的不同,有的语言能支持对 HTTP 请求的编码方式进行自动判断功能(PHP、Java、Perl 等),但是基于以下原因,不建议使用字符编码自动检测功能。
- 在只考虑到 Shfit_JIS 的应用程序中,如果输入数据中混有 Unicode 的 U+00A5 的话,则在经过转义处理后再转换为 Shfit_JIS 编码的话,有可能 U+00A5 会被错误转换为 0x5C(反斜线),导致发生系统漏洞。
- 如果字符编码方式自动检测存在缺陷的话会导致判断结果不正确,从而发生乱码等现象。
所以要尽量避免使用输入数据的自动编码检测功能,而是通过手工显式地进行编码方式设置。
16 此处手机指的是功能机。现在的智能机上的浏览器几乎都支持 Unicode。——译者注
17 最近虽说有一些手机浏览器已经开始支持 UTF-8 编码了,但是 2010 年 12 月以前 au/KDDI 的主页上明确要求使用 Shift_JIS 编码,不能使用 UTF-8。
18 输入字符编码自动选择的时候则存在混入 U+00A5 等 Shift_JIS 中不存在的字符的风险。
19 手机上的邮件客户端也能处理 UTF-8 编码的邮件了。据说这是为了支持 iPhone 的邮件客户端在特定的条件下发送的 UTF-8 编码的邮件。
20 中文一般使用的编码方式为:UTF-8、GB2312 和 GBK。——译者注
21 此测试不适用于中文。中文的乱码问题不像日语那么复杂。——译者注
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论