功能说明
Unicode 中不同文字系统存在形状几乎相同的字符,例如西里尔字母 а 与小写拉丁 a、希腊字母 ο 与拉丁 o。攻击者可以把用户名中的拉丁字母替换成西里尔字母,注册一个肉眼难以分辨的账号,用于冒充客服、品牌或同事。仅做 Unicode 规范化无法解决,因为 NFKC 不会把西里尔字母映射成拉丁字母。
本例把字母按码点区间归类为拉丁、西里尔、希腊或其他,统计一个字符串中出现的文字系统。纯拉丁名称正常;只要同时出现拉丁和另一种文字,就标记为可疑。explain 进一步列出每个非拉丁字母的字符、Unicode 名称和所属文字,便于人工复核。
关键代码
SCRIPT_RANGES 用闭区间描述三类字母的主要码点范围,拉丁范围包含扩展区,能覆盖常见带音调字母。script_of 遍历范围返回文字名称,不在表中的字符归为其他。find_mixed_scripts 只统计字母,去掉数字、标点和表情,再排序返回。is_suspicious 要求同时包含拉丁且文字种类大于一,避免把纯中文或纯俄文名称误判。explain 用列表推导筛出非拉丁字母,并调用 unicodedata.name 取得可读名称。
使用注意
码点区间是粗略分类,某些字母会落在多个文字的共享区,实际部署应使用 Unicode Script 属性或专门的安全库。该函数只能提示风险,不能证明恶意;国际化产品中混合文字可能是正常需求。还应对标识符做 NFKC 规范化、限制方向控制符和零宽字符,并对域名使用 punycode 检查。最稳妥的策略是注册时限制允许的文字集合,或对高价值账号启用人工审核。
验证方式
运行脚本,paypal 与 admin 只包含拉丁字母,is_suspicious 为 False;pаypal 中第二个字母是西里尔 а,因此文字列表包含 CYRILLIC 与 LATIN,结果为 True。explain 输出该字符的 Unicode 名称 CYRILLIC SMALL LETTER A,证明同形字符被准确定位。
代码示例
import unicodedata
SCRIPT_RANGES = {
"LATIN": [(0x0041, 0x024F), (0x1E00, 0x1EFF)],
"CYRILLIC": [(0x0400, 0x04FF)],
"GREEK": [(0x0370, 0x03FF)],
}
def script_of(char: str) -> str:
code = ord(char)
for name, ranges in SCRIPT_RANGES.items():
if any(start <= code <= end for start, end in ranges):
return name
return "OTHER"
def find_mixed_scripts(text: str) -> list[str]:
scripts = {script_of(char) for char in text if char.isalpha()}
return sorted(scripts - {"OTHER"})
def is_suspicious(text: str) -> bool:
scripts = find_mixed_scripts(text)
return "LATIN" in scripts and len(scripts) > 1
def explain(text: str) -> list[dict[str, str]]:
return [
{
"char": char,
"name": unicodedata.name(char, "UNKNOWN"),
"script": script_of(char),
}
for char in text
if char.isalpha() and script_of(char) not in {"LATIN", "OTHER"}
]
if __name__ == "__main__":
for sample in ["paypal", "pаypal", "admin"]:
print(sample, find_mixed_scripts(sample), is_suspicious(sample))
print(explain("pаypal"))