“怎么就被钓鱼了?链接看着是 аррӏе.com 啊。“——这正是问题:看着是,码位完全不同。Unicode 能表示 14 万+ 字符,视觉上相似的不少,真要骗你的眼睛易如反掌。
几个经典同形组
拿肉眼最易骗过去的几组:
| 视觉 | 拉丁 | 西里尔 | 希腊 | 其他 |
|---|---|---|---|---|
| a | U+0061 | а U+0430 | α U+03B1 (近似) | 𝑎 U+1D44E |
| e | U+0065 | е U+0435 | 𝐞 U+1D41E | |
| o | U+006F | о U+043E | ο U+03BF | |
| p | U+0070 | р U+0440 | ρ U+03C1 | |
| c | U+0063 | с U+0441 | ||
| x | U+0078 | х U+0445 | χ U+03C7 | |
| i | U+0069 | і U+0456 | ι U+03B9 |
所以 apple.com 和 аpple.com(首字母西里尔 а)在常见字体下肉眼难分。
数字和符号也有同形
0vsOvs〇(U+3007)vsΟ希腊大写1vslvsIvs|vs丨-vs–连字符 vs—破折号 vs﹣全角.vs。(句号)vs.(全角)vs·(中点)
用户名、密码、商品 SKU 里混进这些非常难查。
IDN 和 Punycode
国际化域名(IDN)允许非 ASCII 字符,但底层 DNS 只认 ASCII——所以会被转码为 xn-- 开头的 Punycode:
аррӏе.com (全西里尔字母)
xn--80ak6aa92e.com (Punycode 真面目)
浏览器的安全策略:
- 纯单一脚本(比如全中文
苹果.com):直接显示原文 - 脚本混合 / 全字符都能被 ASCII 替代:显示 punycode
2017 年 Xudong Zheng 注册了 xn--80ak6aa92e.com 在当时的 Chrome 上完整显示 apple.com——这个漏洞后来被修复但类似攻击变种不断。
NFC / NFD:看起来一样,字节却不同
汉字 / 带音标拉丁字母可能有两种表示法:
- 组合形式(NFC):
é是 1 个码位 U+00E9 - 分解形式(NFD):
é=e(U+0065) +´组合音标 (U+0301),2 个码位
字符串比较会失败:
"café" === "café" // 可能返回 false
"café".normalize("NFC") === "café" // true
规则:存数据库前统一 NFC。macOS 文件系统存 NFD、Windows 存 NFC,跨平台文件名比对前必须先归一化。
NFKC:更激进的归一化
NFC 保留”兼容字符”区分(比如 ① 和 1),NFKC 把这些也合并:
① → 1
ABC → ABC (全角变半角)
㎏ → kg (兼容字符分解)
カタカナ → カタカナ (半角片假名变全角)
NFKC 适合:搜索、同形字检测、账号查重。 NFKC 不适合:原始文本存储——会丢失信息。
零宽字符:看不见的隐写
几个不可见但占字节的字符:
| 码位 | 名称 | 常见用途 |
|---|---|---|
| U+200B | ZERO WIDTH SPACE | 文本水印、绕过过滤 |
| U+200C | ZERO WIDTH NON-JOINER | 阿拉伯语书写 |
| U+200D | ZERO WIDTH JOINER | Emoji 拼合(family emoji) |
| U+FEFF | BYTE ORDER MARK | 文件开头编码标记 |
| U+2060 | WORD JOINER | 不换行 |
| U+180E | MONGOLIAN VOWEL SEPARATOR | 过去曾是空白,现归类格式字符 |
钓鱼场景:邮件里 PayPal.com 中间插一个 U+200B,用户看到完全正常,但这是一个”从未被注册”的唯一字符串,反钓鱼系统按字符串查不到——通过后再引到真域名。
水印场景:给不同员工的内部文档插入不同排列的零宽字符,泄露时按字节指纹锁定来源。
防御方案
按场景分级:
用户名 / 邮箱 / 域名
- 拒绝非预期脚本(域名只允许 ASCII + 中文,不允许西里尔+拉丁混合)
- 登录时输入做 NFKC 归一化,拒绝含格式字符
- 注册时”相似名拒绝”:新用户名 NFKC 后与已有冲突则拒
富文本内容
- 允许 Unicode 全量,但显式剥离零宽字符
- 对外展示前再做一次 NFC 归一
- 敏感区域(标题、用户名)加”含非预期字符”提示
代码标识符
- 变量名、函数名、API key 强制 ASCII
- 禁用 BiDi 覆盖字符(U+202E 等)——2021 年的 Trojan Source 攻击利用这个在代码里”显示”一份、“执行”另一份
快速体检一段文本
把可疑字符串贴进工具,会得到每个码位:
а U+0430 CYRILLIC SMALL LETTER A
p U+0070 LATIN SMALL LETTER P
p U+0070 LATIN SMALL LETTER P
1 U+006C LATIN SMALL LETTER L ← 实际是 l,不是数字 1
e U+0065 LATIN SMALL LETTER E
. U+002E FULL STOP
c U+0063 LATIN SMALL LETTER C
o U+006F LATIN SMALL LETTER O
m U+006D LATIN SMALL LETTER M
西里尔首字母 + 拉丁字母 l 冒充 1——一眼揪出来。
现成工具
粘文本,列出每个字符的码位、名称、所属脚本、是否可打印;一键做 NFC / NFD / NFKC / NFKD 归一化;支持 \u / &#x; / U+ / UTF-8 八种格式互转——验证同形字、处理零宽字符、调试编码问题一次搞定。