仅利用外部可观测数据,发现组织订阅的 SaaS 服务。专为影子 IT 发现而构建。
▄▄▓▓▓▓▓▓▓▓▓▄▄
▄▓▓▒▒▒▒▒▒▒▒▒▒▒▓▓▄ Yowie · tracking down the SaaS nobody told you about
▓▓▒▒░░░░░░░░░░░▒▒▓▓
▓▒░░ ● ● ░▒▓ Domain acme.com.au
▓▒░░ ▼ ░▒▓ Candidates acme, acmecorp
▓▓▒░░ ╰───╯ ░▒▓▓
▀▓▓▒▒░░░░░░░░░▒▒▓▓▀
▀▀▓▓▓▓▓▓▓▓▓▓▓▀▀
发现组织订阅的 SaaS 服务,仅使用外部可观测数据。专为影子 IT 发现而构建:真正值得发现的服务,正是那些没人告诉过你的服务。
以澳大利亚丛林神秘生物命名,因为你正在追猎某种所有人都坚称并不存在的东西。

786 个签名覆盖 593 家供应商,横跨九个检测器,另有 109 个处于待确认状态。发现结果经过分级,且每一条都附带产生它的记录或响应。
go build -o yowie ./cmd/yowie
需要 Go 1.22 或更高版本。签名包已嵌入二进制文件中,因此只需部署单个文件即可。
yowie [flags] <domain> [candidate ...]
该域名为组织的根域名。候选名称是供应商租户可能以其命名的短字符串——商号、缩写、因收购而沿用的旧品牌。尽可能多地提供候选名称;每一个都是发现租户的又一次机会。若未提供任何候选名称,则使用域名的第一个标签。
yowie acme.com.au acme acmecorp acmegroup
yowie -format html -o report.html acme.com.au acme
yowie -format json acme.com.au | jq '.findings[] | select(.confidence=="high")'
yowie -only dns,spf,dmarc -compact acme.com.au
yowie -signatures ./signatures -validate
| 检测器 | 信号 |
|---|---|
dns | TXT 验证令牌、CNAME、MX 和 NS 记录 |
vanity | 以组织命名的供应商托管租户(acme.zendesk.com) |
http | 针对无 DNS 足迹服务的应用层指纹 |
spf | 完整的 SPF include 图,跟踪嵌套 include 和重定向 |
dmarc | DMARC 聚合/取证报告目标 |
bimi | BIMI 徽标和已验证标记证书托管 |
mta-sts | MTA-STS 策略内容 |
tenant | Microsoft 租户 ID、联合身份提供商和同级域 |
ct | 来自证书透明度日志的子域,经解析和归属 |
使用 -only 或 -skip 选择,例如 -skip ct,http 进行快速的仅 DNS 扫描。
最后两项值得特别说明,因为它们能发现签名列表无法发现的东西。
tenant 向 Microsoft 的未认证端点询问该域属于谁。Autodiscover 的联合信息会返回附加到同一租户的每个域,这通常会暴露业务部门、收购和废弃品牌。每一个都是另一个值得扫描的域。
ct 反转了发现模型。其他所有检查都是针对固定列表询问“这个组织是否使用供应商 X?”,因此它只能找到已经有人编写签名的供应商。CT 则询问“这个组织实际上将其子域指向了什么?”一个团队注册了某个产品并接入了 support.acme.com,就会获得签发的证书,而该证书是公开且永久的。不匹配任何签名的目标会作为线索报告,这就是你找到你不知道要寻找的东西的方式。
发现结果经过分级,而分级正是关键所在:
acme.blogspot.com 可能不是你的 acme。两个独立检测器对同一供应商达成一致,会将“可能”发现提升为“已确认”。每个发现都携带触发它的确切记录或响应,因此报告无需重新运行工具即可站得住脚。
提升有一个例外,它正是“此租户存在”与“此租户属于你”之间的区别——参见准确性下的命名空间说明。
-no-unknown 会丢弃未归属的线索,如果你只想要信号。
-format cli(默认)、json、jsonl、csv、html。使用 -o 写入文件。HTML 报告是自包含的——无外部资源——因此即使通过电子邮件发送或放到共享驱动器上也能正常使用。
进度输出到 stderr,结果输出到 stdout,因此 yowie -format json acme.com > out.json 仍会显示实时进度行。
签名位于 signatures/*.yaml。二进制文件内嵌了它们;-signatures <dir> 或 YOWIE_SIGNATURES=<dir> 改为从磁盘加载,这就是无需重新构建即可迭代的方式。
version: 1
pack: dns-txt
description: "Domain-ownership verification tokens published as TXT records."
defaults:
confidence: high
signatures:
- id: txt-1password
vendor: "1Password"
category: "Identity & Secrets"
type: txt
query: "{domain}"
match:
contains: "1password-site-verification"
{domain} 会展开为根域名,{candidate} 会展开为每个候选字符串。
引用 {candidate} 的签名会针对每个候选字符串各评估一次。
| 类型 | 匹配方式 | 说明 |
|---|---|---|
txt、cname、mx、ns | 对 query 的记录执行 match.contains | 不区分大小写 |
vanity_a、vanity_cname | 基线比较;无 match 块 | query 必须包含 {candidate} |
http | 仅使用 match.present、match.absent 或 match.status | 参见下文的状态处理 |
spf_include | 对解析后的 SPF include 图执行 match.contains | |
dmarc_rua | 对 DMARC 报告目标执行 match.contains | |
cname_target | query 为裸托管后缀 | 应用于由 ct 发现的主机;* 匹配一个标签 |
disabled: true 会搁置一个签名,同时保留其背后的研究。
notes: 会将分析师的指导信息传递到报告中。
编辑后运行 yowie -validate。验证是严格的——未知键会报错,而不是静默忽略——并且这些包还会由 go test 检查,因此一个拼写错误不会悄悄地在未来每次扫描中损失覆盖范围。
| 包 | 内容 |
|---|---|
dns-txt、dns-cname、dns-mx | 验证令牌和委派主机名。已启用。 |
email-spf、email-dmarc | 发件人和 DMARC 处理器。已启用。 |
vanity-tenants、http-endpoints | 租户主机名和应用指纹。已启用。 |
cname-targets | 用于 CT 发现子域的托管后缀。已启用。 |
ai-services | AI 和 LLM 平台。已搁置。 |
file-transfer | 托管文件传输——MOVEit、GoAnywhere、Egnyte。已搁置。 |
finance-procurement | 财务、费用、薪资、采购。已搁置。 |
security-grc | 安全工具和合规自动化。已搁置。 |
graveyard | 未验证条目,以及无法识别运营方的平台。已搁置。 |
有五个包完全以禁用状态发布。它们在结构上完整并通过验证,但其匹配条件从未针对实时租户确认过,因此扫描期间不会评估它们。
这是有意为之。一个 contains 值错误的签名永远不会触发,也永远不会报错——它只会让某个供应商看起来已被覆盖,而实际上并非如此。将这些签名以禁用状态发布,可以将研究保留在仓库中,同时不虚增数字。
目前已有五个签名毕业,在信任其余签名之前,这段记录值得了解:两个按原样正确,一个域名错误,两个域名正确但通道错误——该供应商以 SPF include 的形式出现,而不是条目预测的每客户主机名。请将剩余的搁置条目视为研究笔记,而非潜在覆盖。
-validate 会按包分组打印积压项:
$ ./yowie -validate
786 signatures across 13 packs, covering 593 vendors
...
109 signature(s) parked behind `disabled: true`, awaiting confirmation against a live tenant:
ai-services.yaml (24)
txt-fireflies-ai Fireflies.ai
...
要推广一个:
dig +short TXT known-customer.com | grep -i <vendor> # token signatures
dig +short A acme.egnyte.com # vanity signatures
dig +short A $(openssl rand -hex 6).egnyte.com # ...and its baseline
一个 vanity 租户的响应必须与随机标签不同。disabled: true,并设置一个诚实的置信度。notes:,说明你确认了什么。./yowie -signatures ./signatures -validate如果某个机制最终被证明不可行,保持其禁用状态,并将
负面结果记录在 notes: 中。这与一个可用的签名同样有价值——它
能避免下一个人花一下午重新发现这一点。security-grc
已经包含若干此类记录,解释了为什么 CrowdStrike、Wiz 和 Tenable
没有 vanity 签名:它们将所有客户放在共享控制台上,因此
不存在可针对单个客户检测的内容。
该工具会生成自己的待办清单。未归属的发件人和主机正是 值得为其编写签名的供应商,并且在多次扫描中,它们会按频率 自行排序:
for d in $(cat domains.txt); do ./yowie -format json -quiet "$d"; done \
| jq -r '.findings[] | select(.category|startswith("Unidentified")) | .vendor' \
| sort | uniq -c | sort -rn | head -40
跨不相关组织反复出现的内容才是真实的。这同时还能免费产出已确认的活跃样本——SPF 和 DMARC 证据中包含真实的发件主机名,而这正是那些停放条目所需要的。
许多厂商会对其租户区域使用通配符,因此“能解析”并不能证明什么。每个模板首先会用两个随机标签进行探测,以了解厂商对不存在的租户如何应答。只有当候选者的应答与基线不同时,才会计入。如果两个基线彼此不一致——例如地理 DNS、轮询——比较就毫无意义,此时会跳过该签名并给出警告,而不是去猜测。
NXDOMAIN 基线会产生 Confirmed 发现。通配符基线但应答不同,则产生该签名自身的置信度,并附注说明为何较弱。
| 标志 | 用途 |
|---|---|
-only / -skip | 选择检测器 |
-compact | 每个发现一行 |
-max-evidence | 终端中每个发现显示的证据行数,默认 6(0 表示全部) |
-no-unknown | 丢弃未归属的发件人和主机 |
-warnings | 显示非致命问题;带警告的扫描是部分扫描 |
-ct-limit | 限制从 CT 日志解析的主机名数量(默认 400) |
-ct-timeout | CT 聚合器较慢(默认 60s) |
-nameservers | 覆盖公共解析器 |
-timeout | 整体扫描预算(默认 5m) |
-dns-concurrency / -http-concurrency | 调整吞吐量 |
-dns-timeout / -http-timeout | 每次查询和每次请求的预算 |
-quiet | 抑制进度行 |
-insecure | 跳过 TLS 验证,用于证书损坏的厂商 |
-no-banner / -no-color / -no-evidence | 精简终端输出 |
-help 会列出所有内容。Ctrl-C 会报告已发现的内容,而不是将其丢弃。
CT 检测器会并行查询两个聚合器并合并并集。它过去会在第一个应答的聚合器处停止,这会在不知不觉中牺牲覆盖率:在一次实测比较中,次要聚合器返回了 154 个主机名,而主要聚合器返回了 434 个,是严格的子集。如果其中一个来源失败,你仍会得到结果,并附带警告说明覆盖率低于健康运行;如果两者都失败,你会得到警告,而不是静默的缺口。
-ct-limit 会截断一个已排序的列表,因此它保留的是字母序靠前的主机名,而不是采样。当它生效时,警告会给出真实总数并说明选择是按字母序的——对于大型资产,请提高限制以获得完整覆盖。
HTTP 状态处理取决于匹配风格,因为状态码在每种风格中承担着不同的作用。缺失标记会在厂商“无此租户”文本的缺失时触发,而任何无关的错误页面也满足这一点,因此它默认使用 2xx/3xx 防护。存在标记无论状态如何都是正面证据,不受约束——这很重要,因为“存在但访问被拒绝”是一种正常状态:Auth0 对活跃租户返回 400,S3 和 Cloud Storage 对私有存储桶返回 403,Firebase 对正确锁定的数据库返回 401。当“存在”状态是错误码时,请显式设置 match.status。仅含 match.status 的签名只根据状态码匹配,适用于无论哪种情况都提供相同单页应用外壳的厂商。
当名称存在争议时,以候选者命名的租户最高只能评为 Probable。 厂商租户命名空间是全局的且先到先得,因此 acme.zendesk.com 证明存在一个名为 acme 的租户——而不是证明它是你的 acme。有两条规则适用,均适用于虚荣发现和 HTTP 发现:
四个字符或更短的候选者会被直接视为存在争议。短名称被严重超额占用,而且这种冲突在输出中不可见,因为该租户的解析方式与真实租户完全一样。
一个指名某个组织且与候选对象无关的租户页面,以及该域名,也会被视为存在争议,报告会说明它看到了哪个名称。缩写展开有意不计为关联——共享的缩写正是两个组织想要同一个租户名称的原因,因此将其视为匹配会抑制恰恰值得捕获的案例。