同形字:看着是 apple.com
西里尔字母 а(U+0430)与拉丁字母 a(U+0061)字形一模一样,码点完全不同。攻击者用这类孪生字拼出与真品牌、真域名肉眼无异的字符串,这叫 homograph attack。Unicode 用 UTS #39 来防:一张 confusables 表把孪生字折叠成统一的
skeleton,再加上混合脚本检测。本页与 bidi 的 Trojan Source 是「看不见的攻击面」的两面。
1 · 一对孪生
两个「a」肉眼无法区分,码点与所属 Script 却不同,=== 直接判不等——而用户看不出来。
2 · 混合脚本检测
一个串里若混了多种文字系统,典型如拉丁里掺西里尔,几乎可以断定是冒充:正常单词不会这么写。逐码点标出 Script,与主文字不同的那些标红,是最省事的一道检查。
3 · skeleton 折叠
UTS #39 的判定思路是把每个字符按 confusables 表映射到代表字,得到 skeleton;两个串视觉可混淆,当且仅当 skeleton 相同。真 paypal 与掺了西里尔的 pаypаl 码点不同、skeleton 相同,冒充就是这么成立的。
警示 · 本页内嵌的是 confusables 数据的一个精选子集,覆盖常见的西里尔、希腊与全角孪生字,用于演示算法。生产环境要用完整的 confusables.txt 或 ICU 的 USpoofChecker,那里还包含多字符映射与更细的限制级别。
4 · IDN 与 punycode
域名系统只认 ASCII,非 ASCII 域名靠 punycode 编码后传输,前缀是 xn--。地址栏可能显示成好看的 Unicode,底层解析的却是那串编码——两个长得一样的域名,punycode 完全不同。
建议 · 凡是拿来当标识的字符串——用户名、域名、包名、显示名——都要防同形冒充:先 归一化 折掉兼容变体,再按 UTS #39 算 skeleton 查重、做混合脚本检测。Script 属性的来历见 \p{…} 属性类。
5 · 参考文献
- Unicode. UTS #39: Unicode Security Mechanisms. skeleton 算法、混合脚本限制级别与 confusables 数据的定义。unicode.org
- Unicode. confusables.txt. 孪生字到代表字的完整映射表,本页内嵌的是它的子集。unicode.org
- Unicode. UTS #46: Unicode IDNA Compatibility Processing. 域名的 Unicode 处理与 punycode 转换规则。unicode.org
- MDN. URL. 本页用来取真实 host 的接口,其解析走 IDNA。developer.mozilla.org