news 2026/9/14 5:28:13

theHarvester 新增发现模块(Discovery Module)完整接入指南:从 Provider 契约确认到源码注册与测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
theHarvester 新增发现模块(Discovery Module)完整接入指南:从 Provider 契约确认到源码注册与测试

theHarvester 新增发现模块(Discovery Module)完整接入指南:从 Provider 契约确认到源码注册与测试

【免费下载链接】theHarvesterE-mails, subdomains and names Harvester - OSINT项目地址: https://gitcode.com/GitHub_Trending/th/theHarvester

本指南以 theHarvester 官方文档 How to add a new module 为核心骨架,系统讲解如何为这个开源 OSINT 工具新增一个发现源(Provider)。你将掌握一套可复制的六步流程:确认 Provider 契约、实现异步适配器、注册 Source 目录与工厂、配置 API Key、编写离线测试、更新操作员文档,并深入理解SourceExecutionReport状态机、AsyncFetcher会话生命周期等底层机制,最终让新模块能够被theHarvester -s <source>正常调用并产出规范化证据。

0. 前置准备:分支、环境与贡献规范

开始实现前,请先阅读仓库根目录的 CONTRIBUTING.md,其中包含分支策略、开发环境搭建、测试运行方式与 Pull Request 要求。在动手写 Provider 之前,还应在项目的 Issues 与已有 Pull Request 中搜索确认:是否已经有人实现过同一 Provider,或仓库是否已将其标记为失效源(例如 tests/lib/test_source_catalog.py 中就有test_dead_threatcrowd_source_is_not_selectabletest_invalid_bitbucket_domain_source_is_not_selectabletest_removed_inert_sources_are_not_supported等契约测试,专门防止失效源重新进入可选列表)。

1. 第一步:确认 Provider 契约(Provider Contract)

在写任何代码之前,先阅读 Provider 官方的 API 文档和条款,明确以下四个关键问题:

  1. 认证字段:需要什么凭据?是 API Key、Token、账号密码组合,还是完全匿名?认证方式直接决定适配器如何构造请求头以及是否需要接入第 4 步的凭据体系。
  2. 请求约束:请求速率限制(rate limit)、分页方式(offset / cursor / page number)、重试策略、终止行为分别是什么?例如百度搜索以pn参数按 10 条一页翻页(见 theHarvester/discovery/baidusearch.py 的 URL 生成逻辑)。
  3. 稳定响应字段:哪些字段可以稳定地映射为 hosts(主机名)、emails(邮箱)、IPs、ASNs、URLs 或 people?这是决定ResultRoute声明与 getter 实现的依据。
  4. 最小请求序列:查询单个域名最少需要哪几步请求(首页请求、分页、可能的轮询)?

同时注意一个规范红线:不要把 Provider 的价格或配额写入仓库文档,应当链接到 Provider 自有文档。仓库文档只描述技术集成方式。

2. 第二步:实现适配器(Adapter)

适配器统一放在theHarvester/discovery/目录下(目录下已存在 baidusearch.py、crtsh.py、virustotal.py 等 50 余个现成实现)。应当复用项目共享的 fetcher、配置、parser 与结果归一化能力,而不是另起炉灶。

2.1 适配器的标准形态

一个标准的适配器通常提供三部分:

  • 初始化器(initializer):接收目标(word/domain)与结果上限 limit,初始化本地结果集合;
  • 异步process()方法:返回SourceExecutionReport | None,是整个 Provider 会话的入口;
  • 只实现实际支持的 getter:如get_hostnames()get_emails()get_ips()get_asns()get_urls()get_results()等。

以最简单的 theHarvester/discovery/subdomaincenter.py 为例,它展示了最精简的形态:__init__中保存self.word、初始化self.results = set()do_search()AsyncFetcher.fetch_all()发起单次 GET 请求;get_hostnames()直接返回结果集合;process(proxy=False)记录代理标志后调用do_search()

稍微复杂的 theHarvester/discovery/baidusearch.py 则展示了更完整的形态:process()内部先尝试 Playwright 无头浏览器(模拟真实浏览器、携带Core.get_browser_user_agent()),失败或缺少playwright库时回退到纯 HTTP 请求;get_emails()get_hostnames()通过共享 parser theHarvester/parsers/myparser.py 从累积的 HTML 中解析邮箱与子域名。

需要强调的纪律:Provider 没有提供的字段绝不能伪造返回;所有结果在返回前必须做归一化(normalize)与去重(deduplicate)。归一化工作最终由 runner 侧统一执行,见下文第 2.2 节。

2.2process()返回值契约:理解SourceExecutionReport状态机

process()只能返回以下五种结果之一,这是适配器与运行器之间的核心契约:

返回值含义
NoneProvider 会话正常完成,包括合法返回零结果的情况。
SourceExecutionReport('completed', reason)源在自然结束前成功停止,例如已达请求的结果上限。
SourceExecutionReport('failed', reason)Provider 或传输层故障导致源结束。
SourceExecutionReport('rate-limited', reason)终端级限流导致源结束。
SourceExecutionReport('partial', reason)Provider 明确确认覆盖不完整。

在源码层面,theHarvester/lib/source_execution.py 定义了SourceReportStatus = Literal['completed', 'partial', 'failed', 'rate-limited']和冻结数据类SourceExecutionReportstatus必须是上述四种之一,stop_reason必须是非空字符串,否则在__post_init__中直接抛出ValueError

注意:适配器上不得定义可变的execution_statusstop_reason字段。运行器在构造适配器后立即通过_reject_removed_execution_fields()检查这两个字段是否存在,一旦发现直接抛ValueError(见 theHarvester/lib/source_runner.py)。

运行器run_source()(theHarvester/lib/source_runner.py)对返回值的最终化逻辑如下:

  • process()返回None→ 状态为completed;若结果数为 0 且无 stop_reason,则自动记录为completed+no-results
  • 返回SourceExecutionReport→ 采用其状态与 stop_reason;
  • 若结果数 > 0 但状态不是completed,则自动提升(promote)为partial,保留已归一化的证据;
  • 传输层代理失败(AsyncFetcher.proxy_transport_failed()为真)→ 强制failed+transport-error
  • MissingKeyErrorskipped+missing-credentials
  • asyncio.CancelledError→ 保留已收集结果,有结果记partial否则记failed,并通过commit_cancelled回调提交后重新抛出;
  • 其他异常 → 有结果记partial否则记failed,异常类型写入 stop_reason。

因此适配器作者只需对 Provider 会话本身负责,最终状态由运行器统一裁决,这正是"不要定义可变执行字段"的根本原因。

2.3 拥有 Provider 会话(Own the Provider Conversation)

"Provider 会话"指一次源执行相关的完整请求序列:首次请求、分页、重试或轮询、最终响应处理。规范要求这个序列有唯一明确的属主,并遵守以下纪律:

  • 复用同一个AsyncFetcher.open_session():连接池、请求头、cookie jar 与选定的代理身份在会话期间保持稳定。运行器会为一次执行固定一个被选中的代理(通过AsyncFetcher.proxy_scope()上下文管理器传入),适配器要用传入的 proxy 标志打开会话,并把借来的 session 以session=参数传给共享 fetch 方法,只允许最外层属主关闭它
  • cookie jar 策略:当后续请求可能依赖前面响应建立的 cookie 时(典型如搜索引擎的会话 cookie),保留默认 cookie jar;而像接管检测(takeover)这类刻意相互独立的探测,则使用aiohttp.DummyCookieJar(),避免一个目标影响另一个目标。
  • 作用域隔离:一个 session 只服务于一个 Provider 和一个已授权目标,绝不在不同源执行或无关目标之间共享 cookie、认证状态或代理身份
  • 取消安全:关闭每个属主 session、response、task、connector 时必须保留取消语义——既要覆盖成功完成路径,也要覆盖中断路径,且都要有对应测试。open_sessioncore.py中本身就通过drain_tasks_after_cancellation处理了"关闭 session 期间再被取消"的竞态。
  • 生命周期即生命周期阶段:把 session 的构建与销毁视为适配器生命周期阶段。除非 Provider 契约明确改变,否则保留现有 TLS 与超时策略;普通生命周期失败返回SourceExecutionReport,而真正的取消(CancelledError)要让它原样传播。
  • 扩展共享 fetcher 接口需谨慎:在扩展AsyncFetcher这类共享接口前,审计所有位置参数调用点以及每个"属主 vs 借用"分支,新加的可选参数绝不能改变既有调用的语义open_session的完整签名(见 theHarvester/lib/core.py)为headersproxyrequest_timeoutcookie_jarverify,新增适配器应尽量只通过这些既有参数表达需求。

完成检查(completion check):文档要求一个特定的离线测试——后一页的结果依赖于前一页建立的状态(证明浏览器/会话状态在翻页间被保留),外加一条清理断言证明 Provider 会话最终被关闭。这个测试模式在 tests/discovery/test_baidusearch.py 中体现得淋漓尽致:PageResponse.requires_prior_navigation标志会让 FakePage 在"后页丢失浏览器状态"时直接断言失败(见 tests/discovery/test_baidusearch.py),而test_cancellation_survives_cleanup_failures则验证即使 page/context/browser/manager 四层 close 全部抛异常,CancelledError依然原样传播且四个资源都被关闭(tests/discovery/test_baidusearch.py)。

3. 第三步:注册 Source(目录 + 工厂双登记)

适配器写完后需要注册才能被 CLI 识别,注册涉及两个文件、两处条目:

  1. theHarvester/lib/source_catalog.py:新增一条_spec(...)目录条目。目录(catalog)负责提供 CLI 帮助文本、源选择与活动分类(activity classification)。SourceSpec冻结数据类包含nameroutesactivityretains_unresolved_hostnames四个字段;ResultRoute枚举定义了SUBDOMAINS / EMAILS / IPS / ASNS / PEOPLE / URLS / BREACHES七种结果路由(theHarvester/lib/source_catalog.py);ActivityClass枚举定义了PASSIVE('P0') / DNS('P1') / DIRECT('P2')三个活动等级(theHarvester/lib/source_catalog.py)。例如:

    _spec('mynewsource', ResultRoute.SUBDOMAINS, ResultRoute.EMAILS, activity=ActivityClass.PASSIVE),

    目录还支撑resolve_sources()的能力选择器:all会展开为全部 PASSIVE 源,subdomains/emails等能力关键字会按capabilities展开(theHarvester/lib/source_catalog.py)。

  2. theHarvester/lib/source_runner.py:在SOURCE_FACTORIES字典中新增一条工厂条目,用SourceRequest构造适配器实例,例如:

    'mynewsource': lambda request: mynewsource.SearchMyNewSource(request.target, request.limit),

    create_source()(theHarvester/lib/source_runner.py)通过目录中的规范名称查表构造适配器;_ROUTE_GETTERS(theHarvester/lib/source_runner.py)把ResultRoute映射到具体的 getter 名(如SUBDOMAINS → get_hostnames),运行器随后收集所有声明的结果路由并随完成的 run 一起持久化。对于特殊源(如 builtwith 的框架/语言/服务器/CMS/分析栈 getter、hudsonrock 的 infostealer、shodan 的 host 证据),运行器还有专门的_collect_observations分支(theHarvester/lib/source_runner.py)。

最后一条纪律:保持公开的源标识符稳定,且在目录、工厂、CLI、README 矩阵等所有位置使用完全相同的拼写(大小写敏感的源名如securityTrails会被get_source_spec()做 casefold 归一化处理,但规范名本身要一致)。

4. 第四步:按需添加凭据

如果源接受 API Key,按以下四步接入项目统一的凭据体系:

  1. 在 theHarvester/data/api-keys.yaml 中添加空的凭据字段。文件顶层是apikeys:映射,每个 Provider 一个二级映射,字段按需定义。多字段示例:censys需要token+organization_idfofa需要key+emailtomba需要key+secret;单字段示例:shodanvirustotal都只需要key
  2. Core._API_KEY_FIELDS注册这些字段(theHarvester/lib/core.py)。该ClassVar字典把 Provider 名映射到字段元组,Core.api_key_fields()Core._api_key_value()都依赖它。
  3. 添加匹配的Core访问器供适配器使用。例如Core.shodan_key()内部调用_api_key_value('shodan')返回单个值;Core.fofa_key()返回(key, email)二元组;Core.censys_key()则是从api_keys()中安全取tokenorganization_id(theHarvester/lib/core.py)。
  4. 缺少必需凭据时要清晰失败。运行器会捕获MissingKeyError并把该源标记为skipped/missing-credentials,不会中断整个采集(见 theHarvester/lib/source_runner.py);如果 key 是可选的,则保留文档中说明的无 key 行为(如 baidu 不要求凭据即可匿名搜索)。

安全红线:绝不在日志中打印凭据,绝不在测试、示例、提交、Issue 或 Pull Request 中包含真实 key。仓库的api-keys.yaml只存放空字段占位,用户运行时会自动复制到主目录配置路径(Core._read_config会按配置目录查找,找不到则从数据目录创建默认文件)。

5. 第五步:添加聚焦的测试覆盖

文档推荐以 tests/discovery/test_baidusearch.py 为模板——它是一个小而完整的示例,可用pytestmonkeypatch替换网络获取层(包括伪造 Playwright API、伪造AsyncFetcher.open_session/fetch、伪造asyncio.sleep),并断言归一化后的结果。pytestmark = pytest.mark.provider_contract('baidu')把它挂入 Provider 契约测试组。

需要覆盖的典型用例(对应 baidu 测试中的具体测试):

  • 成功解析:如test_process_queries_site_first_and_reuses_one_browser,断言三页 URL 顺序、domcontentloaded等待策略、60 秒超时、headless=True启动参数、代理注入、每页间 1.0 秒延迟,以及邮箱/主机名解析结果;
  • 缺少必需凭据:断言抛出/记录MissingKeyError,最终状态为skipped
  • 非成功响应 / 超时 / 空响应 / 畸形响应:如test_http_429_is_reportedrate-limited+http-429)、test_empty_response_is_reportedfailed+no-response)、test_http_fallback_reports_malformed_responsefailed+invalid-response)。注意项目在 theHarvester/discovery/provider_response.py 提供了共享的provider_http_error()分类器:非FetcherResponsetransport-error,401/403 →access-denied,429 →rate-limited+http-429,其余非 2xx →http-{status},可直接复用;
  • 分页与终止:如test_unlimited_stops_when_provider_repeats_a_page,验证无 limit 时遇到重复页返回partial+repeated-page并停止;
  • 执行报告:不完整工作返回SourceExecutionReport,正常完成返回None
  • 归一化与去重:如test_later_captcha_preserves_partial_results验证部分结果被保留。

硬性约束:测试不得依赖外部网络访问,不得使用真实 Provider 凭据。所有网络行为都必须被 mock 或 stub 替换。

6. 第六步:更新操作员文档

  • 在仓库根目录 README.md 的 Source 矩阵中新增该源,注明其结果路由(routes)、活动类别(activity class)与凭据要求。README 矩阵契约测试会逐项对照目录条目校验这些值是否一致(相关契约测试见 tests/lib/test_source_catalog.py 与 tests/test_readme.py)。
  • 在 Pull Request 描述中链接 Provider 官方 API 文档,并解释任何对共享传输行为的有意例外(例如某源必须禁用重定向、必须使用特定 UA、必须用无头浏览器而非普通 HTTP 等)。

7. 自检清单与工作流全景

把以上六步串起来,一次完整的"新增发现模块"工作流是:

  1. 阅读 CONTRIBUTING.md,检查 Issues/PR 避免重复劳动;
  2. 确认 Provider 契约(认证、限流、分页、稳定字段、最小请求序列);
  3. 在 theHarvester/discovery/ 实现适配器(初始化器 +process()+ 按需 getter),严格遵守SourceExecutionReport状态契约与会话生命周期纪律;
  4. 在 theHarvester/lib/source_catalog.py 加目录条目、在 theHarvester/lib/source_runner.py 加工厂条目,保证标识符拼写一致;
  5. 按需在 theHarvester/data/api-keys.yaml 与Core._API_KEY_FIELDS注册凭据字段并添加访问器;
  6. 参考 tests/discovery/test_baidusearch.py 编写全离线的聚焦测试;
  7. 更新 README.md 矩阵,在 PR 中链接 Provider 文档并说明传输行为例外。

运行新源时,用户通过 CLI 指定源名(如python theHarvester/theHarvester.py -d example.com -s mynewsource)或使用all/ 能力选择器,运行器会按目录条目校验、构造适配器、执行会话、收集并归一化证据、以SourceExecution记录执行状态(completed/partial/failed/rate-limited/skipped)持久化到 run 中——这正是"目录驱动、运行器拥有最终状态"这一架构设计的完整闭环。

【免费下载链接】theHarvesterE-mails, subdomains and names Harvester - OSINT项目地址: https://gitcode.com/GitHub_Trending/th/theHarvester

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 5:25:44

金融行业Agent落地:权限治理、数据隔离与审计的工程实践

Agent在金融行业里喊了好几年&#xff0c;真正敢在生产环境跑起来的并不多。不是不愿意&#xff0c;而是不敢。金融机构面对的不只是"AI能不能完成任务"&#xff0c;而是"AI出错了谁负责、数据去了哪里、权限有没有失控"这一连串相当现实的问题。WorkBuddy…

作者头像 李华
网站建设 2026/9/14 5:25:37

DMM5565同步采样原理与高精度电参数测量实战指南

1. DMM5565不是万用表&#xff0c;而是精密电参数测量系统的“指挥官”很多人第一次看到DMM5565这个型号&#xff0c;下意识就把它当成一台高级数字万用表——毕竟名字里带“DMM”&#xff08;Digital Multimeter&#xff09;&#xff0c;面板上也有电压、电流、电阻档位标识。…

作者头像 李华
网站建设 2026/9/14 5:24:36

园区共享储能与需求响应的Matlab优化实践

1. 项目概述 "含共享储能的园区多类型负荷需求响应经济运行研究"是一个典型的能源管理系统优化课题&#xff0c;主要针对工业园区这类用电负荷集中的场景。随着可再生能源占比提升和电力市场化改革深入&#xff0c;如何通过储能系统和需求响应机制实现园区经济高效运…

作者头像 李华
网站建设 2026/9/14 5:23:00

虚拟机文件传输:SCP、SFTP与Rsync实战指南

1. 虚拟机文件传输需求背景在重邮的计算机相关课程实验中&#xff0c;我们经常需要在本地主机和虚拟机之间传输实验代码、数据文件或配置文档。传统U盘拷贝方式不仅效率低下&#xff0c;还容易造成版本混乱。掌握高效的文件传输方法&#xff0c;是每个计算机专业学生的必备技能…

作者头像 李华
网站建设 2026/9/14 5:22:33

用TensorFlow实现LeNet-5:从MNIST手写数字识别入门卷积神经网络

简介&#xff1a;这是一份基于TensorFlow构建LeNet-5卷积神经网络的手写数字识别项目&#xff0c;网络结构包含卷积层、池化层、全连接层等典型模块&#xff0c;面向毕业设计、课程设计、工程实训和大作业等场景。资源内含MNIST数据集压缩文件、Python训练脚本、多轮迭代后的模…

作者头像 李华