NC3-TestingPlatform/subdomainenum
GitHub: NC3-TestingPlatform/subdomainenum
一款 Python 编写的子域名枚举 CLI,编排多种被动与主动工具完成子域名发现、DNS 解析和虚拟主机模糊测试。
Stars: 0 | Forks: 0
# 子域名枚举
**subdomainenum** 通过被动工具(subfinder、findomain、assetfinder —— 这些工具在内部也会查询 crt.sh 和其他 CT 日志)发现子域名,可选择使用 gobuster 暴力破解 DNS,通过 ffuf 对虚拟主机进行模糊测试,解析每个结果,并打印带有颜色标识的摘要。
```
$ subdomainenum check example.com
```




## 目录
- [功能](#features)
- [依赖要求](#requirements)
- [安装说明](#installation)
- [外部工具](#external-tools)
- [CLI 用法](#cli-usage)
- [Python API](#python-api)
- [Docker](#docker)
- [项目结构](#project-structure)
- [运行测试](#running-tests)
- [贡献指南](#contributing)
## 功能
| 来源 / 模式 | 类型 | 作用 |
| ------------------ | ------- | ------------------------------------------------------------------------------------ |
| **subfinder** | 被动 | 运行 `subfinder -d domain -silent -all` (查询所有来源) |
| **findomain** | 被动 | 运行 `findomain --target domain --quiet` |
| **assetfinder** | 被动 | 运行 `assetfinder --subs-only domain` |
| **dnsrecon** | 被动 | 结合 Bing/Yandex/crt.sh (`-b -y -k`) 运行 `std,srv`,SPF 反向解析 (`-s`),AXFR 区域传输 (`-a`),以及 DNSSEC 区域遍历 (`-z`)。AXFR 和区域遍历针对的是域名的权威 nameserver(公共 DNS 基础设施),而不是目标应用程序——因此它们被归类为被动方式。当环境中存在 `SHODAN_API_KEY` 时,会添加 `--shodan --shodan-active`。 |
| **gobuster dns** | 主动 | 使用字典暴力破解 DNS (`gobuster dns --domain domain -w wordlist`) |
| **ffuf** | 主动 | 通过 `Host` 头模糊测试虚拟主机。目标 IP 会自动派生 —— `--url` 是可选的 (参见 [Vhost 模糊测试](#vhost-fuzzing))。 |
| **DNS 解析** | — | 所有被发现的 FQDN 都会被解析 (每个 FQDN 并行解析 A + AAAA 记录) — `A`/`AAAA` 查询会在共享的 256 个工作线程池中并发执行,最终批次最多由 100 个并行 worker 进行解析。`StreamingResolver` 将 DNS 解析与枚举过程重叠:每个工具在解析出 FQDN 时立即将其推送到解析器中,因此当枚举完成时,大多数查询也已经完成。 |
被动和主动来源可以独立运行,也可以组合运行 (`--mode all`)。
在 `--mode all` 模式下,被动池(4 个 worker)和主动池(1 个 worker:gobuster)会并发运行。随后 `ffuf` 会为每个已解析的目标 IP 分配一个 worker (上限为 8 个)。在构建 `ffuf` URL 时查询到的 IP 会被缓存,因此不会对任何 FQDN 进行两次 DNS 解析。
## 依赖要求
- Python ≥ 3.11
- [`dnspython`](https://www.dnspython.org/) ≥ 2.6
- [`rich`](https://github.com/Textualize/rich) ≥ 13.7
- [`typer`](https://typer.tiangolo.com/) ≥ 0.12
- [`psycopg2-binary`](https://pypi.org/project/psycopg2-binary/) ≥ 2.9
- [`cryptography`](https://cryptography.io/) ≥ 42
外部工具是可选的 —— 缺失的工具会被自动跳过。运行 `subdomainenum info` 以查看哪些工具可用。
## 安装说明
**从源代码安装(推荐):**
```
git clone https://github.com/NC3-TestingPlatform/subdomainenum.git
cd subdomainenum
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]" # installs the CLI + all dev/test dependencies
```
随后即可在你的 shell 中使用 `subdomainenum` 命令。
## 外部工具
运行 `subdomainenum info` 以检查在 `$PATH` 中检测到了哪些工具:
| 工具 | 安装方法 |
| ----------- | -------------------------------------------------------------------------- |
| subfinder | `go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest` |
| findomain | 从 https://github.com/Findomain/Findomain/releases 下载 |
| assetfinder | `go install github.com/tomnomnom/assetfinder@latest` |
| dnsrecon | `pip install git+https://github.com/darkoperator/dnsrecon.git@master` (在 Docker 镜像中从源代码安装) |
| gobuster | `go install github.com/OJ/gobuster/v3@latest` |
| ffuf | `go install github.com/ffuf/ffuf/v2@latest` |
## CLI 用法
### 被动枚举(默认)
```
# 查询 subfinder, findomain, assetfinder, dnsrecon passive (通过 -k 使用 crt.sh)
subdomainenum check example.com
```
### 主动枚举(DNS 暴力破解 + vhost 模糊测试)
```
# 使用 wordlist 进行 DNS 暴力破解
subdomainenum check example.com \
--mode active \
--wordlist /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt
# 暴力破解 + vhost 模糊测试 — ffuf 自动在所有已解析的 IP 上运行
subdomainenum check example.com \
--mode active \
--wordlist /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt \
--wordlist /opt/SecLists/Discovery/Web-Content/raft-small-words.txt
# 将 ffuf 固定到特定 IP(例如负载均衡器 VIP 或内部主机)
subdomainenum check example.com \
--mode active \
--wordlist /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt \
--url http://10.0.0.1
```
### 所有来源组合
```
# 被动 + 主动,vhost 模糊测试自动定向到所有发现的 IP
subdomainenum check example.com \
--mode all \
--wordlist /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt
# 使用特定 URL 覆盖 ffuf 目标
subdomainenum check example.com \
--mode all \
--wordlist /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt \
--url http://10.0.0.1
```
### Vhost 模糊测试
只要提供了字典,ffuf 就会自动启动 —— 无需 `--url` 参数。
**如何选择目标 IP:**
1. 解析基础域名 (`example.com`) (A + AAAA 记录)。
2. 在被动阶段发现的每个子域名会通过共享的 `StreamingResolver` 并行解析 —— 会直接复用枚举期间已缓存的结果,不产生额外的 DNS 开销。
3. 收集所有唯一的 IP 并进行去重。IPv6 地址会用方括号括起来 (`[::1]`)。
4. ffuf 会在每个 IP 上启动一次,最多 8 个 worker 并行执行,针对 `http://` 模糊测试 `Host` 头。
如果指定了 `--url`,则会直接使用该 URL,且不会进行自动解析。
**ffuf 实际在什么时候运行?**
| 模式 | ffuf 运行吗? | 目标来源 |
|------|-----------|---------------|
| `passive` | 否 | — |
| `active` | 是(如果提供了字典) | 基础域名 IP + 主动枚举子域名 IP |
| `all` | 是(如果提供了字典) | 基础域名 IP + 被动子域名 IP |
结果会进行去重:在多个 IP 中发现的相同虚拟主机只会出现一次。
### JSON 输出
```
# 机器可读输出 (stdout)
subdomainenum check example.com --json
# 保存到文件
subdomainenum check example.com --json --output report.json
```
### 调试日志
```
# 将每个工具的原始输出保存到自动命名的日志文件中
subdomainenum check example.com --debug-log
# 同样适用于 --json
subdomainenum check example.com --json --debug-log
```
当指定 `--debug-log` 时,每个工具输出的每一行都会被写入一个名为 `_YYYYMMDD_HHMMSS.log` 的文件中。如果挂载了 `/reports/` 卷 (Docker),文件将放置在该目录中,否则放在当前目录下。调试信息不会输出到 stderr —— 结束时会出现一句简短的 `Debug log → ` 确认提示。
### DNS 超时
```
# 调整每次查询的 DNS 解析超时时间(默认 5.0 s)
subdomainenum check example.com --timeout 10
```
### 工具可用性
```
subdomainenum info
```
### 版本
```
subdomainenum --version
```
## Python API
### 完整评估
```
from subdomainenum.assessor import assess
from subdomainenum.models import EnumMode
from subdomainenum.reporter import print_report
report = assess(
"example.com",
mode=EnumMode.PASSIVE, # passive | active | all
wordlist=None, # required for active/all
url=None, # optional: pin ffuf to one URL; omit to auto-target all resolved IPs
timeout=5.0, # DNS resolution timeout per query
progress_cb=print, # optional: called with status strings
)
print_report(report)
```
### 处理结果
```
from subdomainenum.assessor import assess
report = assess("example.com")
print(report.domain) # "example.com"
print(report.mode.value) # "passive"
# 子域名
for sub in report.subdomains:
print(sub.fqdn, sub.status.value, sub.ip_addresses, sub.tools)
# 虚拟主机 (ffuf,仅在带 --url 的主动/全部模式下可用)
for vhost in report.vhosts:
print(vhost.vhost, vhost.status_code, vhost.content_length)
# 按工具划分的结果
for tool in report.tools:
print(tool.name, len(tool.subdomains), tool.available, tool.error)
```
`Status` 值:`ALIVE`、`DEAD`、`TIMEOUT`、`ERROR`、`FOUND`、`NOT_FOUND`、`SKIPPED`。
### JSON 序列化
```
import json
from subdomainenum.assessor import assess
from subdomainenum.reporter import to_dict
report = assess("example.com")
print(json.dumps(to_dict(report), indent=2))
```
顶层 schema:
| 键 | 类型 | 备注 |
|---|---|---|
| `domain` | string | 目标基础域名 |
| `mode` | string | `"passive"`、`"active"` 或 `"all"` |
| `subdomains[]` | array | `{fqdn, status, alive, ip_addresses, tools}` |
| `vhosts[]` | array | `{vhost, status_code, content_length}` |
| `tools[]` | array | `{name, count, available, error, timed_out, mode}` |
每个 `tools[]` 条目反映了单次工具运行的整个生命周期:`available=false` 表示缺少相应的二进制文件或 API;`timed_out=true` 表示该工具被总时长或空闲超时看门狗杀死(与 `error` 不同);当工具引发或报告错误时,`error` 是一个字符串,否则为 `null`。
## Docker
通过附带的 `Dockerfile`,提供了一个预装所有工具并捆绑了 SecLists(DNS + Web-Content 目录)的 Docker 镜像。
```
# 构建镜像
docker compose build
# 被动检查
docker compose run subdomainenum check example.com
# 使用捆绑的 SecLists wordlist 进行主动检查
docker compose run subdomainenum check example.com \
--mode active \
--wordlist /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt
# 所有模式 + vhost 模糊测试,将报告保存到主机 ./reports/
docker compose run subdomainenum check example.com \
--mode all \
--wordlist /opt/SecLists/Discovery/DNS/subdomains-top1million-5000.txt \
--url http://10.0.0.1 \
--json \
--output /reports/example.json
# 检查容器内的可用工具
docker compose run subdomainenum info
```
## 项目结构
```
subdomainenum/
├── subdomainenum/
│ ├── __init__.py Package version
│ ├── models.py Dataclasses: SubdomainResult, VhostResult,
│ │ ToolResult, EnumReport + Status/EnumMode enums
│ ├── dns_utils.py resolve_ips(), is_alive() — dnspython wrappers
│ │ (A+AAAA queried in parallel on a shared pool)
│ ├── streaming.py StreamingResolver — background DNS queue that
│ │ overlaps resolution with enumeration
│ ├── constants.py ACTIVE_TOOLS registry, detect_tools(), get_install_hint()
│ ├── assessor.py assess() — orchestrates passive + active sources
│ ├── reporter.py Rich terminal output + to_dict() + save_report()
│ ├── verdict.py build_verdict() — factual count summary
│ ├── cli.py Typer CLI: check, info sub-commands
│ └── tools/
│ ├── tool_runner.py subprocess wrapper used by all active tools
│ ├── subfinder.py subfinder wrapper
│ ├── findomain.py findomain wrapper
│ ├── assetfinder.py assetfinder wrapper
│ ├── dnsrecon.py dnsrecon wrapper (std,srv + Bing/Yandex/crt.sh/SPF
│ │ + AXFR + DNSSEC zone walk; always passive)
│ ├── gobuster_dns.py gobuster dns wrapper (sole DNS brute-forcer)
│ └── ffuf.py ffuf vhost fuzzing wrapper
├── tests/
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_verdict.py
│ ├── test_constants.py
│ ├── test_dns_utils.py
│ ├── test_assessor.py
│ ├── test_reporter.py
│ ├── test_cli.py
│ └── tools/
│ ├── test_tool_runner.py
│ └── test_wrappers.py
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml
├── requirements.txt
├── requirements-dev.txt
└── README.md
```
## 运行测试
```
source .venv/bin/activate
# 带覆盖率运行所有测试(通过 pyproject.toml 自动配置)
pytest
# 快速运行(简短的 tracebacks)
pytest --tb=short -q
# 运行单个模块
pytest tests/test_assessor.py -v
# 运行单个测试类
pytest tests/test_cli.py::TestCheckCommand -v
```
测试套件包含 **338 个测试**,并在所有模块中达到了 **99% 的测试覆盖率**。
所有的 DNS I/O (`dns.resolver.Resolver.resolve`)、TLS sockets 以及 subprocess 调用都在边界处进行了 mock —— 没有任何测试会连接到真实服务器或互联网。
## 贡献指南
1. Fork 该仓库并创建一个功能分支。
2. 添加或更新测试 —— 该项目的目标是达到 80% 以上的单元测试覆盖率。
3. 在提交 pull request 之前运行 `pytest` 并确认所有测试通过。
4. 遵循现有的 docstring 格式 (reStructuredText / docutils 字段列表)。
5. 使用 [约定式提交](https://www.conventionalcommits.org/):
`fix:`、`feat:`、`refactor:`、`test:`、`docs:`、`chore:`
## 许可证
GPLv3 —— 详见 [LICENSE](LICENSE)。
标签:amass, assetfinder, Bug Bounty, CT日志查询, dnsrecon, DNS安全, DNS枚举, DNS爆破, DNS解析, ffuf, findomain, GitHub, gobuster, Python命令行工具, subfinder, 主动发现, 可自定义解析器, 域名侦察, 子域名扫描器, 子域名枚举, 安全工具库, 开源项目, 系统安全, 网络安全工具, 虚拟主机发现, 被动侦察, 请求拦截, 逆向工具