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

574 条签名,覆盖 412 家供应商,分布在九个检测器中,另有 104 条处于待确认状态。发现结果经过分级,每一条都附带产生它的记录或响应。
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 徽标和 Verified Mark Certificate 托管 |
mta-sts | MTA-STS 策略内容 |
tenant | Microsoft 租户 ID、联合身份提供商和同级域名 |
ct | 来自 Certificate Transparency 日志的子域名,经解析并归属 |
使用 -only 或 -skip 选择,例如 -skip ct,http 进行快速的仅 DNS 扫描。
最后两个值得说明,因为它们能发现签名列表无法发现的东西。
tenant 询问 Microsoft 的未认证端点该域名属于谁。Autodiscover 的联合信息会返回附加到同一租户的每一个域名,这通常会暴露出业务部门、收购和废弃品牌。每一个都是另一个值得扫描的域名。
ct 反转了发现模型。其他所有检查都是针对固定列表询问“这个组织是否使用供应商 X?”,因此它只能找到已经有人写过签名的供应商。CT 则询问“这个组织实际上把它的子域名指向了什么?”一个团队注册了某产品并配置了 support.acme.com,就会获得一张证书,而那张证书是公开且永久的。不匹配任何签名的目标会作为线索报告,这正是你找到自己不知道要去找的东西的方式。
发现结果经过分级,而分级正是关键:
acme.blogspot.com 可能不是你的 acme。两个独立检测器对同一供应商达成一致,会将 Probable 发现提升为 Confirmed。每一条发现都附带触发它的确切记录或响应,因此报告无需重新运行工具即可站得住脚。
该提升有一个例外,它正是“这个租户存在”与“这个租户是你的”之间的区别——参见 准确性 下的命名空间说明。
-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
574 signatures across 13 packs, covering 412 vendors
...
104 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 状态处理取决于匹配风格,因为状态码在每种风格中承担不同的作用。absent 标记在缺少供应商的“无此租户”文本时触发,而任何不相关的错误页面也满足这一点,因此它默认带有 2xx/3xx 防护。present 标记无论状态如何都是正面证据,不受约束——这很重要,因为“存在但访问被拒绝”是正常状态:Auth0 对活跃租户应答 400,S3 和 Cloud Storage 对私有存储桶应答 403,Firebase 对正确锁定的数据库应答 401。当“存在”状态是错误码时,请显式设置 match.status。只有 match.status 的签名仅按状态码匹配,适用于无论哪种情况都提供相同单页应用外壳的供应商。
当名称存在争议时,以候选名称命名的租户被限制为 Probable。 供应商租户命名空间是全局的且先到先得,因此 acme.zendesk.com 证明存在一个名为 acme 的租户——而不是它就是你的 acme。有两条规则适用,均适用于 vanity 和 HTTP 发现:
四个字符或更少的候选名称直接视为存在争议。短名称被严重超额占用,而且这种冲突在输出中不可见,因为该租户的解析方式与真实租户完全一样。
如果租户页面列出了一个组织,且该组织与候选名称和域名无关,也视为存在争议,报告会说明它看到了哪个名称。缩写扩展故意不算作关联——共享缩写正是两个组织想要同一租户名称的原因,因此将其视为匹配会压制掉恰恰值得捕捉的情况。