~/dnsniffer/guides·品牌保护
$ dnstwist --registered example.com

如何查找仿冒抢注域名

生成近似域名是容易的那一半。真正要紧的另一半,是弄清楚其中哪些确实被人注册了、哪些构成真实威胁。这里给出全部可行方法,以及配套命令与分级规则。

阅读约 16 分钟
# 摘要

简短回答

先生成品牌名的各种排列,然后拿它们去比对一份已知存在的域名库,而不是为每个候选发一次 DNS 查询。对剩下的结果按三个字段排序:MX 记录A 记录TLS 证书。这三项能把值得处理的少数几个,与几百个只是停放着的字符串区分开。

  • 只是给自家品牌做一次审计?dnstwist 加一次域名库比对。一个下午,不用预算。
  • 需要持续检测?用你的排列列表筛查每日新域名数据集,同时跟读证书透明度日志。要在注册当天发现它,而不是在它被用来攻击你的那天。
  • 要决定升级处理哪一个?从自己的解析日志入手。组织内部从未解析过的近似域名,优先级低于上周被十个人访问过的那个。
方法能发现什么速度成本投入
生成排列(dnstwist、urlcrazy)已知名称的经典拼写错误按需运行免费
检索已注册域名库任何含有品牌字符串的已注册域名,含组合抢注即时免费
证书透明度监测近似域名签发 TLS 证书后数秒内即可发现数秒至数分钟免费
筛查每日新域名数据集昨天注册的全部域名,按你自己的规则过滤每天免费
自有解析日志 + 被动 DNS员工和客户实际访问到的那些近似域名数小时免费至付费
商业品牌保护服务覆盖面广,并代为执行下架每天付费
TMCH 商标提示新 gTLD 中完全一致的注册,在注册当刻提示注册时付费(按年)
# 先说清楚

仿冒抢注,以及另外四种被叫作仿冒抢注的东西

严格意义上的仿冒抢注,是注册一个别人打错字就会进入的域名。实际使用中,五个不同类别被归进同一个词,而它们需要不同的检测手段 - 排列生成器只能找到前两类,对其余的完全无感:

  • 1. 拼写抢注 - 键盘距离造成的错误。exapmle.comexmple.comexamlpe.com。排列生成即可找到。
  • 2. 后缀抢注 - 同一字符串换个后缀。example.coexample.cmexample.app。同样属于排列,但 TLD 列表要你自己提供。
  • 3. 组合抢注 - 品牌拼写完全正确,再加一个词。example-login.comsecure-example.netexample-support.co。这是真实钓鱼中最常见的一类,任何拼写错误生成器都造不出来,只有在已注册域名中做子串检索才能找到。
  • 4. 同形字 / IDN 仿冒 - 显示上完全一样的字符。用西里尔字母 a 冒充拉丁字母 a、用 rn 冒充 m、用 1 冒充 l。它们以 punycode(xn--)形式存储,所以逐字节比对永远发现不了。
  • 5. 比特抢注 - 名称在内存或传输中被翻转了一个比特。fxample.com 之于 example.com。少见但真实存在,生成成本也很低。
还有第六种,它根本不是域名注册:子域名仿冒,攻击者拥有 evil.com,然后提供 example.com.login.evil.com。任何注册监测都找不到它,因为没有新域名被注册。只有证书透明度和你自己的日志分析能发现。
# 第 1 步

生成候选集合

dnstwist 是参考实现,也是几乎所有人最后都会用的工具。它一次性套用所有排列类别 - 漏字、插字、重复、换位、相邻按键替换、元音互换、加连字符、同形字、比特翻转 - 并可选地检查哪些候选能够解析。urlcrazy 随 Kali 附带,用不同的词表做同一件事。

三种运行方式,由省事到彻底:

# 1. Permutations only, no network - this is your candidate list
dnstwist --format list example.com > candidates.txt
wc -l candidates.txt

# 2. Same sweep, but resolve as it goes and keep only what exists
dnstwist --registered --format csv example.com > registered.csv

# 3. The two fields that decide priority later: MX, and a fuzzy hash of the
#    live page compared against the real one (--ssdeep on older builds)
dnstwist --registered --mx --lsh ssdeep --format json example.com > registered.json

# 4. Cross the brand with a TLD dictionary, not just the suffix you own
printf '%s\n' com net org co io app shop online site xyz > tlds.txt
dnstwist --tld tlds.txt --format list example.com | sort -u > candidates.txt

example 这样一个词,在加上 TLD 之前大约会产生一千到三千条排列。再与一份 TLD 列表相乘,候选量就到了几万条 - 这正是下一步不能是“逐个解析”的原因。

要喂对字符串。从品牌词生成,而不是从完整域名生成。对 example.com 做排列会连 .com 一起排列,那是噪声;对 example 做排列,再与你自己挑的 TLD 列表相乘,得到的集合才是可以推敲的。挑你所在行业真正被滥用的那些 TLD,而不是全部 1400 个。
# 第 2 步

把候选变成已确认的注册

现在你手里有几千个字符串,却不知道哪些真的存在。有四种查证方式,其中三种比看上去更糟:

  • DNS 解析 - 快且免费,但它只能证明该名称有 A 或 NS 记录。已注册但没有配置名称服务器的域名解析不出任何结果,你会漏掉它;而且负缓存会在 SOA 最小值的时长内持续返回 NXDOMAIN。
  • 逐个候选查 RDAP 或 WHOIS - 权威,但各注册局的速率限制严到让两万条候选变成好几天的活。只用在候选短名单上,绝不要用在整个候选集合上。
  • 在区域文件里搜索 - 对你拿到 CZDS 批准的那些 gTLD 是准确的,每个区域好几个 GB,而且对几乎所有 ccTLD 都是盲区。
  • 到现成的域名库里去查 - 一次查询代替几千次,没有速率限制,还能覆盖你拿不到区域文件的 TLD。下一节讲的就是这个。
无论选哪种,先做归一化:转小写、去掉结尾的点、把 IDN 转成 punycode,并用 Public Suffix List 归约到可注册域名。Example.CO.UK.example.co.uk 是同一条发现;靠数点号来判断可注册部分,正是 foo.co.uk 被解析错的原因。
# 第 3 步

检索已注册域名库

这里就是你正在浏览的站点,请据此权衡本节内容。它之所以属于这份清单,是因为问题的形状恰好如此:DNSniffer 把 gTLD 区域文件和覆盖所有 TLD 的证书透明度数据,汇入同一张可检索的域名表 - 目前收录 358,047,798 个活跃域名,覆盖 9,957 个 TLD,搜索接口公开、无需账号,并且支持通配符。于是问题从“这个候选存在吗”问两万遍,变成“有哪些长得像它的东西存在”只问一次。

通配符是这里的关键:* 匹配任意长度的字符,所以 exam*le 一次查询就能把词中间的插入、删除和替换全都覆盖到。

# 1. Every indexed domain containing your brand string, any TLD
curl -s 'https://dnsniffer.com/api/v1/domains?q=example&limit=1000' \
  | jq -r '.data[].name'

# 2. "*" is a wildcard, so one query covers a missing, extra or swapped
#    character anywhere in the middle of the token
curl -s 'https://dnsniffer.com/api/v1/domains?q=exam*le&limit=1000' \
  | jq -r '.data[] | [.name, .tld, .cert_issuer.organization // "-"] | @tsv'

# 3. Narrow to one TLD
curl -s 'https://dnsniffer.com/api/v1/domains?q=example&tld=com&limit=1000'

# 4. Screen today's new domains against the candidate list from step 1
URL=$(curl -s 'https://dnsniffer.com/api/v1/datasets?category=newly_detected' \
      | jq -r '.data[] | select(.periodicity=="daily") | .download_url')
curl -sL "https://dnsniffer.com$URL" -o nrd-daily.zip
unzip -p nrd-daily.zip | tail -n +2 | cut -d, -f1 | sort -u > today.txt
comm -12 today.txt <(sort -u candidates.txt) > hits.txt

在依赖它之前要知道两个边界。这份数据由观测构成 - 区域委派与证书签发 - 所以一个既未委派、也从未申请过证书的已注册域名不在其中。另外,命中只说明该名称存在,并不说明它何时注册;凡是你要采取行动的对象,都用 RDAP 复核。

如果需要的是持续检测而不是一次性审计,有两样东西在服务端运行。监测为每个账号保留最多十条子串关注,新收录的域名一旦匹配就发邮件摘要。新发现域名导出把过去 24 小时内收录的全部域名做成 CSV,你的排列列表正好拿它来筛。最新域名页面则是同一条数据流的浏览器视图。

子串关注不是模糊匹配。它能抓到 example-login.com,却会漏掉 exarnple.com,因为后者并不包含你的字符串。关注覆盖的是组合抢注那一类;拼写错误那一类,仍然要靠你生成的列表去筛每日数据集。
# 第 4 步

在证书签发的那一刻抓住它们

近似域名一旦开始提供页面就变得危险,而如今几乎所有钓鱼页面都走 HTTPS - 也就意味着有证书,也就意味着签发后数秒内会出现一条公开的证书透明度记录。CT 是本页唯一能在第一个受害者到达之前就告诉你的方法,也是唯一能看见子域名仿冒的方法。

两种接入方式:接一个聚合后的实时流,或者按 RFC 6962 接口直接读日志。无论哪种,过滤器都由你自己写,而模糊匹配就应该放在过滤器里 - 你面对的是一条名称数据流,而不是在检验一份固定的候选清单。

# Aggregated firehose, filtered to names close to your brand
websocat wss://certstream.calidog.io \
  | jq -r '.data.leaf_cert.all_domains[]?' \
  | sed 's/^\*\.//' \
  | python3 -c '
import sys, unicodedata, idna
from rapidfuzz.distance import Levenshtein

BRAND = "example"

def decode(name):
    try:
        return idna.decode(name) if "xn--" in name else name
    except Exception:
        return name

def fold(s):
    # Cheap confusable folding. A real one uses the Unicode confusables table.
    s = unicodedata.normalize("NFKD", s)
    for a, b in (("rn", "m"), ("vv", "w"), ("1", "l"), ("0", "o"), ("5", "s")):
        s = s.replace(a, b)
    return s

for line in sys.stdin:
    name = line.strip().lower()
    label = fold(decode(name)).split(".")[0]
    if BRAND in label:                       # combosquat
        print("substring", name, flush=True)
    elif Levenshtein.distance(label, BRAND) <= 2:   # typo / homoglyph
        print("distance ", name, flush=True)
'

证书带有 SAN,所以一张证书会产生很多名称。先把每个都归约到可注册域名,然后跑三种检测,因为每一种都看不见另外两种能抓到的东西:与品牌词的编辑距离不超过 2 能找出拼写错误那一类,朴素的子串匹配能找出组合抢注那一类,而先解码 xn-- 标签再做易混淆字符归一化,才能找出同形字那一类。

证书续期会淹没这条数据流。务必维护一份永久的首次出现集合,否则每隔六十天证书轮换时,你会对同一个域名重复告警。
# 第 5 步

先读自己的 DNS 日志

上面所有方法找的是存在的近似域名。你的解析日志找的是真正起作用的近似域名,那是一份短得多、也紧急得多的清单。对 DNS 或代理日志做两次查询,价值超过大多数付费数据源:

  • 与自家域名只差一点的 NXDOMAIN 应答。那是你自己的员工和客户在打错字。其中每一个名字,别人明天就能注册下来,从此开始收走投递错的邮件。把这份清单的头部自己注册掉,是本页最便宜的一项措施。
  • 成功解析了与你相近、但不属于你的名称。此刻就有人在你的网络里访问一个近似域名。那是安全事件,不是监测发现。
# Unbound / BIND query log -> names your users typed that do not exist,
# ranked by how many times they typed them
grep NXDOMAIN /var/log/unbound/query.log \
  | awk '{print tolower($NF)}' \
  | python3 -c '
import sys
from rapidfuzz.distance import Levenshtein
for name in sys.stdin:
    name = name.strip().rstrip(".")
    label = name.split(".")[0]
    if 0 < Levenshtein.distance(label, "example") <= 2:
        print(name)
' | sort | uniq -c | sort -rn | head -20

被动 DNS 把同样的思路延伸到你的边界之外:服务商记录合作递归解析器观测到的解析行为,为每个名称给出首次出现时间。它适合用来确定某个近似域名何时上线,同时先天带有偏差 - 你只能看到他们传感器网络看到的部分。

# 第 6 步

付费服务、下架与仲裁

品牌保护服务商 - MarkMonitor、CSC、ZeroFox、Bolster 以及一长串其他公司 - 把近似域名监测和下架执行打包出售。监测并不是难的那部分,上面五节已经能复现其中大半。你买的是后半部分:与注册商和主机商长期建立的联系,以及提交并持续追进的滥用举报。如果你的团队里没人能腾出一个周二下午,去和另一个法域的注册商掰扯,那这笔钱是值的。

确认一个域名确实在作恶之后,有四条路,由快且便宜的排起:

  1. 1. 向注册商和主机商举报滥用 - 免费,而且对正在运行的钓鱼站点是最快的一条路。把证据同时发给注册商的滥用联系人和主机商:URL、截图、时间戳,以及它在冒充谁。一个正在窃取凭据的页面可能几小时内就被拿下;一个只是停放着的近似域名则通常被忽略。
  2. 2. 提交到拦截名单 - Google 安全浏览、APWG、PhishTank,以及你自己的 EDR 或 DNS 过滤。这能在几分钟内保护你的用户,而其余流程都按法律的节奏走。
  3. 3. URS - 约 375 美元起,数周内出结果。它只在域名剩余注册期内将其暂停,并不会转让给你,而且要求清楚而有说服力的证据。适合证据确凿的钓鱼,对任何存在争辩空间的情形都没用。
  4. 4. UDRP - 独任专家组、覆盖少量域名的案子,机构费用约 1500 美元,另加律师费和大约两个月时间。它会把域名转让给你。你必须同时证明三点:与你的商标构成混淆性近似、持有人对该域名没有正当权益、以及恶意注册并使用。
有一项预防措施值得知道,尽管它不属于检测:把注册商标录入商标信息交换库(TMCH),可获得新 gTLD 的日升期资格,并在一个 TLD 开放后的前 90 天内获得 Claims 提示 - 有人注册完全一致的名称时会通知你。仅限完全一致:example-login 不会触发。
# 第 7 步

给发现结果排序

对一个有知名度的品牌做一轮排列扫描,通常会返回几百个已注册的近似域名。它们绝大多数只是停放着,而挨个去追正是品牌保护项目走向死亡的方式。按“是否在实际运营”的证据排序,信号最强的排在前面:

  • 存在 MX 记录。该域名能收邮件。这一项每次都排在最前 - 发票诈骗和商业邮件入侵正是这样开始的,而且完全不需要网站。
  • A 记录指向一个活着的 HTTP 服务。把它抓下来看。注册商的停放页是噪声;一份你登录表单的复制品是安全事件。
  • 已签发证书。说明有人做过配置。免费 DV 证书让这一项单独看时证据很弱,与 A 记录合起来看时证据很强。
  • 页面相似度。把渲染后页面的模糊哈希,或者干脆只把 favicon 的哈希,与你自己的做比较。dnstwist --lsh 能帮你做前者;favicon 哈希比对很粗糙,却出奇地有效。
  • 注册时间与注册商。上周刚在一家滥用记录不佳的注册商处注册,与某人从 2013 年起一直持有,是完全不同的风险。到这一步,RDAP 复核才值得去承受它的速率限制。
  • 被你在意的人解析过。与自己的解析日志做交叉比对。这会把一条监测命中变成一张带有影响范围的工单。
# Rank confirmed lookalikes by how operational they are
while read -r d; do
  a=$(dig +short A "$d" | head -1)
  mx=$(dig +short MX "$d" | head -1)
  [ -n "$a$mx" ] && printf '%s\tA=%s\tMX=%s\n' "$d" "${a:--}" "${mx:--}"
done < hits.txt | sort -t= -k3 -r

# Authoritative registration date for the shortlist only - RDAP rate-limits
while read -r d; do
  curl -s "https://rdap.org/domain/$d" \
    | jq -r --arg d "$d" '.events[]? | select(.eventAction=="registration")
                          | "\($d),\(.eventDate)"'
  sleep 1
done < shortlist.txt > shortlist-dates.csv
不要只凭距离就动手。确实存在名称与你相近的合法企业,把编辑距离 2 以内的域名一律封禁,一定会弄坏某个客户需要的东西。先打分,再复核头部,然后只对经得起复核的那些采取行动。
# 常见坑

什么会让一轮近似域名扫描出错

  • 拿 punycode 去算距离。xn--exmple-4of.com 按字节看离 example.com 十万八千里,在屏幕上却一模一样。比较任何东西之前,先解码每一个 xn-- 标签。
  • 相信 NXDOMAIN。已注册但没有名称服务器的域名解析不出来。没有 DNS 应答不等于没有注册。
  • 数点号来判断可注册域名。foo.co.ukfoo.uk 遵循不同规则。请使用 Public Suffix List。
  • 只生成,从不检索。没有任何排列生成器能造出 example-account-verify.com。省掉在已注册域名中做子串检索这一步,整个组合抢注类别对你就是不可见的 - 而那恰恰是真实钓鱼中出现的类别。
  • 对续期重复告警。没有永久的首次出现集合,每一次证书续期看起来都像一条新发现,你的队列会被三月份就已经分级过的域名塞满。
  • 没有章法地做防御性注册。把最常见的五十个错拼买下来是合理的;买五千个就是一笔没有出口的长期账单,而排列空间实际上是无限的。只注册你自己的 NXDOMAIN 日志能证明确实有人在打的那些,把它们跳转到真实站点,其余交给监测。
# 常见问题

常见问题

? 什么是仿冒抢注(typosquatting)?

注册一个别人打错字就会进入的域名,比如用 exapmle.com 对应 example.com。目的各不相同:有的投放广告、把误入流量变现,有的跳转到竞争对手,有的把名字攥在手里等着卖给品牌方,还有的直接搭钓鱼页面,或者截收发往错拼地址的邮件。这套手法很老却一直有效,因为它利用的是人的失误,而不是技术漏洞。

? 怎样找出针对我品牌的仿冒抢注域名?

两轮,两轮都要做。第一轮:用 dnstwist 或 urlcrazy 生成品牌词的各种排列,查清其中哪些已被注册,这覆盖拼写错误、后缀替换和同形字三类。第二轮:把品牌当作子串,在已注册域名库里检索,这覆盖任何生成器都造不出来的组合抢注类。之后按 MX 记录、A 记录和证书给命中结果排序,只对你准备处理的那份短名单用 RDAP 核对注册日期。

? 仿冒抢注违法吗?

取决于意图和司法辖区,这也正是存在仲裁机制而非一刀切规则的原因。在美国,《反域名抢注消费者保护法》(ACPA)规定,出于恶意牟利意图注册与显著商标构成混淆性近似的域名可被追究。在国际层面,UDRP 要求同时满足三点:与你的商标构成混淆性近似、持有人对该域名没有正当权益、以及恶意注册并使用。仅仅注册一个与常用词相似的名称,本身并不违法。

? 仿冒抢注、组合抢注和域名抢注有什么区别?

域名抢注(cybersquatting)是上位概念,指出于恶意注册域名以利用他人商标。仿冒抢注是其中依靠拼写错误的那个子集。组合抢注则把你的品牌拼写正确,再加上一个词,例如 example-support.com、example-login.net;它在真实钓鱼中更常见,也更难检测,因为品牌字符串本身是正确的,而可以附加的词是无限的。

? 防御性注册应该买多少个错拼域名?

比服务商告诉你的要少。真正有回报的集合很小:两三个最常见的拼写错误、客户默认你会使用的那少数几个后缀,以及你自己的 NXDOMAIN 日志能证明确实有人在打的名称。把它们跳转到真实站点,其余的靠监测。防御性注册是按域名按年重复支出的成本,而排列空间实际上是无限的 - 你买不出去这个坑。

? 什么是同形字攻击,怎么检测?

用显示上完全一样的字符做替换:西里尔字母 a 冒充拉丁字母 a、希腊字母 omicron 冒充 o、字母组合 rn 冒充 m。这类名称以 punycode 存储,因此以 xn-- 开头,逐字节与你的品牌比对什么都找不到。检测的做法是:解码每一个 xn-- 标签,用 Unicode 的易混淆字符映射表、或者自己维护一张常见替换表,把这些字符归一化到规范形式,然后才去度量相似度。浏览器在显示层面做了缓解,当一个标签混用了多种文字时会直接显示 punycode,但这对你的监测没有帮助。

? 怎样让一个仿冒抢注域名被下架?

先做最快的。如果它正在钓鱼,就同时向注册商的滥用邮箱和主机商举报,附上截图、URL、时间戳以及它在冒充谁;这可能几小时内解决,而且不花钱。同时把该 URL 提交给 Google 安全浏览和各类钓鱼拦截名单,这能在其余流程按法律节奏推进时先保护你的用户。如果你要的是域名本身而不只是让它消失,就提起 UDRP:机构费用约 1500 美元,另加律师费和大约两个月时间,之后域名会转让给你。URS 更便宜也更快,但只会把域名暂停到其注册期结束。

? 可以免费监测新出现的仿冒抢注域名吗?

可以,而且主要是个调度问题。跟读证书透明度日志,对流过的名称跑你的相似度过滤器,代价是一个常驻的小进程。或者每天下载一份新发现域名导出,每天用你的排列列表和子串规则筛一遍,那就是一个定时任务。DNSniffer 同时提供每日导出和无需账号的公开搜索 API,免费账号还可以设置最多十条子串关注并收到邮件摘要。商业品牌保护值得买的时候,是你需要有人替你做下架工作,而不是你需要检测能力。

用自己的排列列表去筛每日数据集,是一个定时任务,不是一个项目。新发现域名导出每天重建、免费下载 - 当前 24 小时文件中有 463,220 个域名,而域名检索无需账号即支持通配符。