Skip to main content

10. Security Considerations (安全考虑)

UTF-8的实现者需要考虑如何处理非法UTF-8序列的安全方面。在某些情况下,攻击者可以通过向其发送UTF-8语法不允许的八位字节序列来利用不谨慎的UTF-8解析器。

主要安全威胁

1. 过长编码攻击 (Overlong Encoding Attack)

这是一种特别微妙的攻击形式,可以针对以下解析器进行:

  • 对UTF-8编码形式的输入执行安全关键的有效性检查
  • 但将某些非法八位字节序列解释为字符

攻击示例1: NUL字符绕过

正常禁止: 00 (NUL字符)
解析器正确拒绝: 00

过长编码攻击: C0 80 (非法的2字节编码)
易受攻击的解析器错误地将其解释为: NUL字符

危险: 绕过了安全检查!

场景:

  • 解析器可能禁止编码为单八位字节序列 00 的NUL字符
  • 但错误地允许非法的两八位字节序列 C0 80 并将其解释为NUL字符

攻击示例2: 路径遍历攻击 (实际发生过!)

正常禁止: 2F 2E 2E 2F  ("/../")
解析器正确拒绝: /../

过长编码攻击: 2F C0 AE 2E 2F
/ ..非法.. ./
易受攻击的解析器错误地将其解释为: /../

危险: 目录遍历攻击成功!

真实案例:

  • 这种利用实际上已在2001年攻击Web服务器的广泛传播的病毒中使用
  • 安全威胁是真实存在的!

详细分析

字符: '.'
合法编码: 2E (1字节)
过长编码: C0 AE (2字节,非法)
E0 80 AE (3字节,非法)
F0 80 80 AE (4字节,非法)

攻击向量:
输入: /etc/passwd
绕过检查: /..C0AE./..C0AE./etc/passwd
如果解析器不正确处理: 成功访问 /../../../etc/passwd

2. 缓冲区溢出风险

编码为UTF-8时的另一个安全问题:

历史遗留问题

ISO/IEC 10646的UTF-8描述允许编码字符编号高达 U+7FFFFFFF,产生最多6字节的序列。

风险

因此,如果以下情况,存在缓冲区溢出的风险:

  • 字符编号的范围没有明确限制为 U+10FFFF
  • 缓冲区大小没有考虑5字节和6字节序列的可能性

正确的限制

有效Unicode范围: U+0000 到 U+10FFFF
最大字节数: 4字节
无效范围: U+110000及以上

防御措施: 严格验证字符范围,拒绝超过U+10FFFF的编码。

3. 规范化问题

字符编码的一个特征(包括UTF-8)也可能影响安全性:

多重表示问题

"同一事物" (就用户而言)可以由几个不同的字符序列表示。

示例:带重音的e

方式1: 预组合字符
U+00E9 (E ACUTE, é)
UTF-8: C3 A9

方式2: 规范等价序列
U+0065 U+0301 (E + COMBINING ACUTE)
UTF-8: 65 CC 81

用户看到的: é (完全相同)
字节序列: 完全不同

安全影响

即使UTF-8为每个字符序列提供单一的字节序列,"同一事物"存在多个字符序列的事实在涉及以下操作时可能产生安全后果:

  • 字符串匹配 (String matching)
  • 索引 (Indexing)
  • 搜索 (Searching)
  • 排序 (Sorting)
  • 正则表达式匹配 (Regular expression matching)
  • 选择 (Selection)

安全示例

场景: 访问控制
凭证中的标识符: "café" (使用U+00E9)
访问控制列表: "café" (使用U+0065 + U+0301)

问题: 字节级别不匹配!
结果: 访问被拒绝(或被授予,取决于实现)

安全风险: 认证/授权绕过

解决方案:Unicode规范化

使用Unicode规范化形式 [UAX15]:

规范化形式:
- NFC (Normalization Form Canonical Composition)
- NFD (Normalization Form Canonical Decomposition)
- NFKC (Normalization Form Compatibility Composition)
- NFKD (Normalization Form Compatibility Decomposition)

推荐: 在安全关键操作前进行规范化

安全最佳实践

1. 严格验证

def validate_utf8(data):
"""严格的UTF-8验证"""
# 1. 检查字节序列语法
if not is_valid_syntax(data):
raise InvalidUTF8Error("Invalid byte sequence")

# 2. 检查过长编码
if has_overlong_encoding(data):
raise InvalidUTF8Error("Overlong encoding detected")

# 3. 检查代理对
if has_surrogates(data):
raise InvalidUTF8Error("Surrogate pairs not allowed")

# 4. 检查范围
if exceeds_unicode_range(data):
raise InvalidUTF8Error("Character exceeds U+10FFFF")

return True

2. 拒绝非法序列

必须 (MUST) 拒绝:
✗ 过长编码 (C0 80 for NUL)
✗ 代理对 (ED A0 80)
✗ 超出范围 (F4 90 80 80)
✗ 截断序列 (E4 BD)
✗ 孤立的后续字节 (80)
✗ 无效的首字节 (C0, C1, F5-FF)

3. 规范化

import unicodedata

def secure_compare(str1, str2):
"""安全的字符串比较"""
# 规范化为NFC形式
norm1 = unicodedata.normalize('NFC', str1)
norm2 = unicodedata.normalize('NFC', str2)
return norm1 == norm2

4. 限制缓冲区

// C语言示例
#define MAX_UTF8_CHAR_LEN 4 // 不是6!
char buffer[MAX_UTF8_CHAR_LEN * MAX_CHARS + 1];

安全检查清单

检查项重要性说明
✅ 拒绝过长编码⭐⭐⭐⭐⭐防止安全绕过
✅ 验证字符范围⭐⭐⭐⭐⭐限制为U+0000-U+10FFFF
✅ 拒绝代理对⭐⭐⭐⭐U+D800-U+DFFF无效
✅ 检测截断序列⭐⭐⭐⭐防止缓冲区读取错误
✅ Unicode规范化⭐⭐⭐⭐安全关键字符串比较
✅ 缓冲区大小限制⭐⭐⭐防止溢出

真实世界的攻击示例

Code Red II 蠕虫 (2001)

攻击: 使用过长UTF-8编码绕过IIS路径检查
编码: ..%c0%af..%c0%af..%c0%af
等同于: ../../..
影响: 数百万Web服务器被感染

教训

永远不要信任输入!

  • 始终严格验证UTF-8序列
  • 拒绝所有非规范编码
  • 在安全关键操作前进行规范化

相关链接