简介:《OSINT权威指南:尽职调查实务》是面向开源情报初学者与需要更新知识体系的资深从业者的实务型电子书,聚焦商业智能场景下的尽职调查流程与方法。全书由Cynthia Hetherington撰写,第三版更新至2024年,内容覆盖商业调查类型、企业结构梳理、网络数据库与公开信息源检索,以及调查结果的分析呈现,并延伸到SWOT、供应链与价值链分析、社交媒体及行业特定资源的情报收集。资源包仅含1个PDF文件,约6.29MB,便于集中阅读与按章节查阅。已有185人学习下载。读者可从中获得从信息源选择、检索路径设计到结果验证与结构化分析的完整框架,也能借助书中的实践案例和分析工具,理解如何把零散公开信息转化为可用于商业判断的情报依据,适合边学边对照实际调查任务使用。
1. 一份材料看着都对,为什么尽调还是查不出问题
OSINT 在尽职调查里不是一个工具名,而是一条证据链的组装顺序。前段时间帮一个做硬件集成的团队复核供应商准入材料,对方给的东西很齐:营业执照、两份合同扫描件、一张官网截图。真正让我停手的是三件小事——官网域名的首次注册时间比主体成立时间晚了四年;证书透明度记录里,同一张证书还挂着两个从未在材料中出现过的域名;其中一个域名的注册邮箱,和投标联系人邮箱同域。三条全部来自公开来源,全程没有惊动对方。
这就是 OSINT 尽调要解决的问题:在对方不提供、也不知道你在查的前提下,用公开登记信息、基础设施痕迹和公开文本,把“他说的”变成“能对上的”。适合做供应商准入、投前复核、渠道商资质核查、合作方背景排查的人——手上有一堆零散材料,缺的是一套能重复跑的流程,以及一条知道什么时候该停的红线。
2. 实体画像怎么拆:三层字段表与信号可信度矩阵
2.1 主体、关联方、时间线:开搜之前先把空格画出来
新手做 OSINT 尽调最常见的动作是“先搜了再说”,搜完把一堆截图往报告里一贴,看起来信息量很大,实际上一条都用不上。原因很简单:搜索引擎给的是相关性排序,尽调要的是字段级的一致性。正确的顺序是先把要填的空格画出来,再决定去哪个来源填。
我一般把画像拆成三层。主体层回答“是不是这个法人”,关联层回答“它还牵着谁”,时间线层回答“这些事发生的先后顺序自不自洽”。三层里最容易出成果的是第三层,因为时间不会撒谎——主体成立日期、域名首注册时间、证书首次签发时间、商标申请日、公开招标中标日、招聘信息首次出现时间,这几条线的先后关系只要错位,就一定有一个解释,而这个解释往往就是风险点。
| 层级 | 字段 | 主要来源 | 用来判断什么 |
|---|---|---|---|
| 主体层 | 完整注册名称、唯一主体标识(注册号)、注册地址、成立日期、登记状态、经营范围、法定代表人、注册资本 | 企业登记公开库、官方公告 | 主体是否真实存续、经营范围是否覆盖标的业务 |
| 关联层 | 股东与对外投资、同地址其他主体、同联系电话或邮箱、共用域名与证书、共用名称服务器 | 登记库、DNS、证书、公开文本 | 是否存在未披露的关联方或空壳结构 |
| 时间线层 | 成立日、域名首注册日、证书首签发日、商标申请日、中标公告日、招聘信息首次出现日 | 登记库、RDAP、证书透明度、公告平台 | 材料叙事与客观时间是否自洽 |
字段还要再分三类,这个分类决定了你该花多少时间:A 类是能直接查到的硬字段,比如成立日期、登记状态,花十分钟就能拿全;B 类是能推断但需要两条以上证据的软字段,比如实际经营地、真实业务规模;C 类是公开渠道根本拿不到的,比如真实客户名单、实际控制人的个人资产。新手最容易在 C 类字段上耗掉一整天,最后写一句“未能核实”,性价比极低。
提示:C 类字段不是尽调的失败,是尽调的边界。把“查不到”明确写进报告,比含糊带过安全得多。
三层画完之后,每个空格旁边其实已经自带了一个“去哪查”的标签,检索就变成了按图索骥,而不是漫无目的地浏览。这一步多花二十分钟,后面能省两小时。
2.2 给每条线索定级:什么才算一条“事实”
尽调报告最容易被打回的地方,不是结论下得不够狠,而是把“网上看到”直接当成“事实”写。定级规则要先于检索定好,否则你会在写报告的时候反复纠结某条信息到底能不能用。
我用的来源分级是这样:S1 是一手登记信息与官方公告,包括企业登记库、法院或行政机关依法公开的公告、招投标平台的中标公示;S2 是主体自述,包括官网、宣传册、投标文件、新闻通稿;S3 是基础设施痕迹,包括 DNS 记录、证书、域名注册数据;S4 是第三方转载与聚合平台;S5 是社交媒体与个人发布。默认可信度依次递减。
组合规则比单条来源更重要:两条相互独立的 S1 指向同一结论,可以定 A 级;单条 S1 定 B+;只有 S2 的,一律写成“主体主张”,不能进结论段;S4 必须回溯到原始出处,回溯不到的按 S5 处理。这里有一个血泪经验——“独立”两个字经常被自己骗过去:同一篇通稿被十家媒体转载,那是十个渠道、一个来源,不是十条证据。
| 组合方式 | 定级 | 能不能写进结论 |
|---|---|---|
| 两条独立 S1 一致 | A | 可以,直接陈述 |
| 单条 S1 | B+ | 可以,附来源与采集时间 |
| S1 + S3 相互印证 | A- | 可以,注明推断链条 |
| 仅 S2 | C | 不能,只能写成“待验证主张” |
| S1 与 S2 冲突 | 冲突项 | 不能,必须列为待解释项 |
冲突本身是最有价值的产出。一条 S1 和一条 S2 打架,不要急着判断谁对谁错,先把它记成“待解释项”,然后在后续检索里专门找第三条证据。尽调报告的说服力,很大程度上来自“我发现了矛盾,并且给出了处理方式”,而不是“我查到很多东西”。
还有一个容易被忽略的纪律:时间戳。域名注册数据会变,页面会改,公告会下架。每条记录都要带采集时间和原始地址,能用哈希就用哈希,能存本地快照就存快照。没有采集时间的证据,在复核环节等同于没有。
3. 域名与证书:用 RDAP 和证书透明度把主体链路接起来
3.1 一条命令捞出证书里的全部域名
基础设施痕迹之所以好用,是因为它有一个绕不开的约束:证书是为了让人能访问而签发的,只要站点在提供服务,证书就必须公开可查,而且很难长期藏住。一张多域名证书经常一次性暴露同一主体持有的其他站点,这是尽调里性价比最高的一类线索。
取证书不需要任何商业平台,一条命令就够:
# HOST 换成待查域名;只读证书元数据,不校验证书链(对方证书可能已过期或自签) HOST="target-domain.tld" openssl s_client -connect "${HOST}:443" -servername "${HOST}" -showcerts </dev/null 2>/dev/null \ | openssl x509 -noout -subject -issuer -dates -fingerprint -sha256 -ext subjectAltName逻辑说明:s_client完成一次 TLS 握手并把服务端证书链打印到标准输出;x509接住这串 PEM 做解析,-noout表示不重新输出证书体,只打印我们关心的字段。整条链路不依赖网络之外的任何东西,拿到什么就是什么。
参数说明:-servername必须带,它触发 SNI,不带的话共享 IP 的虚拟主机很可能返回一张默认站点的证书——这是取错证书最常见的原因,没有之一。</dev/null是防止s_client握手完成后继续等待标准输入把自己挂住;2>/dev/null屏蔽握手噪音。-ext subjectAltName需要较新的 openssl,老版本改用-text再抓Subject Alternative Name那一段。-fingerprint -sha256出来的值别扔,它是后面聚类的连接键。
输出的subjectAltName里,DNS:后面的每一项都是一个真实在用的域名。把它们全部收集起来,逐个做一次同样的探测,就是第一轮扩散。
3.2 批量采集:把 openssl 输出规整成可比对的 CSV
一条一条手敲不现实,写成脚本跑批,顺便把第二轮扩散也做掉:
import subprocess, re, csv from pathlib import Path HOSTS = Path("hosts.txt") # 每行一个纯域名,不带协议和路径 OUT = Path("certs.csv") def probe(host: str, timeout: int = 10) -> dict: cmd = ["openssl", "s_client", "-connect", f"{host}:443", "-servername", host, "-showcerts"] try: raw = subprocess.run(cmd, input=b"", capture_output=True, timeout=timeout).stdout # 解析失败时 x509 会返回空,这里不抛异常,统一记为 parse_fail txt = subprocess.run( ["openssl", "x509", "-noout", "-subject", "-issuer", "-dates", "-fingerprint", "-sha256", "-ext", "subjectAltName"], input=raw, capture_output=True, timeout=timeout ).stdout.decode("utf-8", "ignore") except subprocess.TimeoutExpired: return {"host": host, "error": "timeout"} if "notAfter" not in txt: return {"host": host, "error": "parse_fail"} san = sorted({s.lower() for s in re.findall(r"DNS:([^,\s]+)", txt)}) return { "host": host, "not_after": re.search(r"notAfter=(.+)", txt).group(1).strip(), "issuer": re.search(r"issuer=(.+)", txt).group(1).strip(), "fp_sha256": re.search(r"Fingerprint=([0-9A-F:]+)", txt).group(1).strip(), "sans": "|".join(san), # 用竖线分隔,方便后续 split 和去重 } rows, seen = [], set() for host in [l.strip().lower() for l in HOSTS.read_text().splitlines() if l.strip()]: if host in seen: continue seen.add(host) rows.append(probe(host)) with OUT.open("w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=["host", "not_after", "issuer", "fp_sha256", "sans", "error"]) w.writeheader() for r in rows: w.writerow({k: r.get(k, "") for k in w.fieldnames})逻辑说明:先对每个域名跑一次握手,把 PEM 流转手给x509做解析,再用正则把关键字段抠出来。解析失败和超时都单独记成error而不是丢掉,因为“这个域名连不上”本身就是一条信息——尤其是它出现在对方官网的备案信息里,却连 443 都握不上手的时候。
参数说明:timeout=10是单域名上限,跨境站点偶尔握手慢,低于 5 秒会误判成失败。seen集合负责去重,因为第二轮扩散会大量回填已经查过的域名。sans用竖线拼接而不是逗号,是为了绕开证书里本身就存在的逗号分隔。
第二轮扩散的规则很简单:把第一轮所有sans里的域名拆出来,剔除通配符(形如*.开头的只保留主域)、剔除与目标明显无关的公共托管域名,回填到hosts.txt再跑一遍。两轮足够,第三轮的收益会急剧下降,而误报会明显上升。
3.3 RDAP 字段归一化:注册时间、注册人、名称服务器
域名注册数据的老接口返回的是自由文本,不同注册局的格式千差万别,没法批处理。RDAP 是它的结构化继任者,返回 JSON,字段稳定,适合写进流水线。
import json, os, time, urllib.request # 服务入口由环境变量注入,不写死地址,便于换源和留痕 RDAP_BASE = os.environ["RDAP_BASE"].rstrip("/") def rdap_domain(domain: str, timeout: int = 10, retries: int = 2): url = f"{RDAP_BASE}/domain/{domain}" req = urllib.request.Request( url, headers={"Accept": "application/rdap+json", "User-Agent": "dd-osint/1.0"}) for i in range(retries + 1): try: with urllib.request.urlopen(req, timeout=timeout) as r: return json.loads(r.read().decode("utf-8")) except urllib.error.HTTPError as e: if e.code == 404: # 域名不存在或无公开记录,不是错误 return None if e.code == 429: # 触发限流,退避后重试 time.sleep(2 ** i) continue raise def normalize(d: dict) -> dict: rec = {"handle": d.get("handle", ""), "status": ";".join(d.get("status", []))} # 事件时间统一转成 ISO8601 字符串,后续可直接排序比较 for ev in d.get("events", []): key = {"registration": "registered", "expiration": "expires", "last changed": "updated"}.get(ev.get("eventAction")) if key: rec[key] = ev.get("eventDate", "") rec["nameservers"] = sorted({ n.get("ldhName", "").lower() for n in d.get("nameservers", [])}) # 注册人邮箱藏在 vcardArray 里,第二项才是真正的属性数组 rec["emails"] = sorted({ v[3] for e in d.get("entities", []) for v in e.get("vcardArray", [[], []])[1] if isinstance(v, list) and len(v) >= 4 and v[0] == "email" }) return rec逻辑说明:rdap_domain只负责取原始 JSON,把 404 和 429 分开处理——404 代表这个域名没有公开记录,属于正常结果;429 是限流,必须退避,否则整批任务会被对方掐断。normalize负责把三种事件名映射成统一字段,把名称服务器转小写去重,把注册人邮箱从 vcard 结构里挖出来。
参数说明:RDAP_BASE走环境变量,换源只需改环境,不动代码,也方便在报告里注明数据来自哪个入口。retries=2配合2 ** i的指数退避,实测足够应付大部分限流窗口。registered字段是这一整套里最重要的一个——它和主体成立日期的差值,就是前面提到的那条时间线。
注意:注册人信息在不少后缀下已被隐私保护遮蔽。看到一屏
REDACTED不要当成失败,把它记成“不可得”,转而从名称服务器和证书方向找关联。
3.4 用并查集把“看起来无关”的域名聚成一簇
单个域名的信息价值有限,价值在于“哪些域名属于同一簇”。这一步用一个并查集就够,不需要任何图数据库。
class DSU: def __init__(self): self.p = {} def find(self, x): self.p.setdefault(x, x) while self.p[x] != x: # 路径压缩,避免深链退化成 O(n) self.p[x] = self.p[self.p[x]] x = self.p[x] return x def union(self, a, b): ra, rb = self.find(a), self.find(b) if ra != rb: self.p[rb] = ra # shared_attrs 的键是 (属性类型, 属性值),值是持有该属性的域名列表 # 权重:email=3,fp_sha256=2,nameserver=1 dsu, evidence = DSU(), {} for (kind, value), domains in shared_attrs.items(): if len(domains) < 2: continue for d in domains[1:]: dsu.union(domains[0], d) evidence.setdefault(dsu.find(domains[0]), []).append((kind, value, len(domains))) clusters = {} for d in all_domains: clusters.setdefault(dsu.find(d), []).append(d)逻辑说明:把每一个共享属性当成一条边,把持相同属性的域名连起来,最后取连通分量就是候选簇。evidence用来记录这个簇是靠什么连起来的,写报告时直接引用。
参数说明:三类属性的权重必须分开——共享注册邮箱(权重 3)几乎可以认定同一控制人;共享证书指纹(权重 2)大概率是同一套部署;共享名称服务器(权重 1)只能算弱信号,因为一个建站服务商底下可能挂着几千个毫无关系的域名。一个簇的成立门槛我定在“总权重 ≥ 3,且至少包含两条不同类型的证据”,低于这个门槛只进附录,不进正文。
4. 公告与合同附件:三要素抽取和“三源一致”的落表方法
4.1 主体、金额、日期:三条正则覆盖八成场景
尽调材料里大头是 PDF 和扫描件转出来的文本:中标公告、合同附件、公开简历、宣传稿。这类文本不需要上大模型,三条正则加一套归一化能覆盖八成场景,而且结果可解释、可复核。
import re, unicodedata PAT = { # 金额:允许千分位,单位限定万元/元,避免把数量、编号误抓成金额 "amount": re.compile(r"(?:金额|合同价|总价|报价|中标价)[^\d]{0,6}" r"([0-9][0-9,\.]*)\s*(万元|元)"), # 日期:只认 20xx 开头的四位年,减少把编号当日期 "date": re.compile(r"(20\d{2})\s*[年\-/\.]\s*(0?[1-9]|1[0-2])\s*[月\-/\.]\s*" r"(0?[1-9]|[12]\d|3[01])"), # 主体名:中文全角括号要一起收,否则会截断 "org": re.compile(r"[\u4e00-\u9fa5()()·]{2,40}?" r"(?:有限公司|有限责任公司|合伙企业|研究院|事务所|集团)"), } def norm_text(s: str) -> str: s = unicodedata.normalize("NFKC", s) # 全角转半角 s = re.sub(r"\s+", "", s) return s.replace("(", "(").replace(")", ")") def extract(text: str) -> dict: t = norm_text(text) return { "amounts": [(float(a.replace(",", "")), u) for a, u in PAT["amount"].findall(t)], "dates": ["-".join(f"{y}-{int(m):02d}-{int(d):02d}" for y, m, d in PAT["date"].findall(t))], "orgs": sorted(set(PAT["org"].findall(t))), }逻辑说明:先做 NFKC 归一化,把全角数字和全角括号拉平,否则同一份材料里“(”和“(”会让主体名截断成两个不同字符串。金额正则强制要求前面出现“金额/总价/中标价”这类触发词,是为了压掉大量误报——纯数字串在公告里太多了,不加锚点召回率上去了,准确率会崩。
参数说明:[^\d]{0,6}允许触发词和数字之间夹少量文字,实测“中标价为人民币”这种结构刚好卡在 4 到 5 个字符,放宽到 6 够用。日期正则限制20\d{2}是有意的取舍,历史项目里偶有 19xx 的日期,但那类材料本身就该人工过一遍。主体名长度上限 40 是防跨句误抓。
4.2 主体名归一:用 difflib 挡掉“看起来一样”的错配
同一家公司在不同材料里可能写成“某某科技(某地)有限公司”“某某科技有限公司”“某某科技”,直接字符串比对会全部对不上;但反过来放宽匹配,又会把两家真不同的公司合并。这一步的阈值必须靠样本调。
import difflib BRACKETS = re.compile(r"[()()]") def canon(name: str) -> str: s = norm_text(name) s = BRACKETS.sub("", s) # 去括号但保留括号内文字 for suffix in ("有限责任公司", "有限公司", "集团"): s = s.replace(suffix, "") return s def same_entity(a: str, b: str, th: float = 0.86) -> bool: ca, cb = canon(a), canon(b) if not ca or not cb: return False if ca == cb: return True # 短名套长名时要求完整包含,不做模糊匹配,避免"某科"命中"某科技" if len(ca) >= 4 and (ca in cb or cb in ca): return True return difflib.SequenceMatcher(None, ca, cb).ratio() >= th逻辑说明:canon只做能确定无害的归一——去括号、去组织形式后缀;括号内的地名或字号保留下来,因为那往往正是区分主体的关键。same_entity先用包含关系处理短名套长名,再退回相似度。
参数说明:阈值 0.86 是在几百对样本上试出来的经验值,低于 0.80 会把“某某联合科技”和“某某联创科技”这类只差一两个字的异主体合并,高于 0.92 又会漏掉大量真实别名。任何一次same_entity判定为真,都必须人工看一遍两条原始字符串——这一步没有后悔药,合错了后面全错。
4.3 三源一致:把交叉验证落成一张表
交叉验证不能停留在脑子里,要落成一张可以贴进报告的矩阵。
| 待验证项 | 材料自述 (S2) | 公开登记 (S1) | 基础设施 (S3) | 公开文本 (S1) | 结论 |
|---|---|---|---|---|---|
| 主体名称 | 某某科技有限公司 | 某某科技有限公司 | — | 中标公示同名 | 一致,A 级 |
| 成立时间 | 称“深耕十年” | 成立 4 年 | 域名首注册 3 年前 | 无 | 冲突,待解释 |
| 办公地址 | 某写字楼 12 层 | 同址登记 6 家企业 | — | 招聘信息同址 | 一致但需关注集群注册 |
列固定成“材料自述 / 公开登记 / 基础设施 / 公开文本”四栏,行是待验证字段,每格填“一致 / 冲突 / 不可得”。填完之后,报告的结构其实已经出来了:一致项进事实段,冲突项进待解释段,不可得项进局限说明段。
提示:整张表里最值钱的是“冲突”那一列。一份没有任何冲突项的尽调报告,通常不是项目干净,而是查得不够。
5. 避坑:OSINT 尽调里最容易翻车的五个地方
5.1 五条踩坑记录,每条都是真的疼
取错证书。现象是抓到的证书和站点内容对不上,甚至证书里的域名和你要查的毫无关系。原因是握手时没带 SNI,或者目标挂在 CDN 后面,返回的是边缘节点的默认证书。解决办法是强制带-servername,并且拿到结果后做一次反向校验:证书的 SAN 列表里必须包含目标域名本身,不包含就判定为无效样本、重新探测。这个校验我后来直接写进了脚本,省了很多二次排查。
把域名注册时间当成公司成立时间。现象是报告里写“该主体实际经营仅一年”,被对方一句“域名是后来换的”顶回来。原因是域名可以转让、可以更换、可以因为迁移而重新注册,它反映的是“这个站点什么时候上线”,不是“这家公司什么时候成立”。解决办法是把时间线层的所有字段都降级成“矛盾提示项”,只用来触发追问,绝不用来单独定性。这条我吃过一次亏,后来所有涉及“成立早晚”的结论都要求两个以上 S1 支撑。
同名主体误配。现象是把 A 公司的行政处罚挂到了 B 公司头上,两家名字只差一个字。原因是只按名称匹配,没有用唯一主体标识或注册地址做二次校验。解决办法定一个硬规则:名称、注册号、注册地址三项里至少两项吻合才认定同一主体,只有名称吻合的一律进“疑似”,不进正文。这条规则写进流程之后,误配率基本归零。
共用名称服务器直接判关联。现象是把两家毫无关系的公司写成关联方,报告当场失去可信度。原因是共用一个建站服务商或一个代理注册商,名称服务器本来就会一样。解决办法是给不同属性分配权重:共享注册邮箱权重最高,共享证书指纹次之,共享名称服务器只能算弱信号,必须再叠加一条独立证据才能成簇。这个教训非常直接——弱信号不是不能用,是不能单独用。
采集没留痕。现象是一个月后复核,页面已经改版,公告已经下架,你手上只剩一句“当时查到过”。原因是只存了结论没存证据。解决办法是把“采集时间 + 原始地址 + 内容哈希 + 本地快照”做成每条记录的必填字段,缺一项就算这条证据不合格。这件事做起来枯燥,但它是整个流程里唯一有效的后悔药。
5.2 一份可以照着走的排查顺序
顺序不是随便定的,原则是“从最便宜、最不容易被修饰的信号开始”。
第一步查登记信息。它最便宜,也最硬,因为改动成本高。第二步查域名与证书,成本中等,造假成本比登记信息低但比宣传材料高。第三步查公开文本,包括公告、判决文书摘要、招聘信息,成本开始上升,噪声也大。第四步才看主体自述,也就是官网和宣传册——把它们放在后面,是为了避免先入为主。第五步才是人,包括公开场合的发言、公开履历,这一步最容易踩到隐私边界,必须放在最后且有明确目的。
每一步的输出都要能回答一个问题:这一步有没有产生新的待解释项?如果没有,就往下走;如果有,就地展开,不要急着走完全流程。新手常见的毛病是把五步都走一遍,攒了五十页材料,却一个待解释项都没深挖。
5.3 采集边界:三条自我约束
第一,只采公开可访问、不需要登录也不需要伪装身份就能看到的内容。需要提交虚假信息才能进入的地方,采到的东西也不能用。第二,不采集与尽调目标无关的个人信息——查到某个联系人的私人社交账号,和“这家公司是否履约能力充足”没有关系,就不要存。第三,不把推断写成结论,凡是推断出来的关联,在报告里必须标注推断链条和所用证据,让别人能推翻你。
留痕本身就是一种约束。当每条记录都被要求写清来源和时间,你会自然而然地少采很多“顺手看到但用不上”的东西,报告的密度反而上去了。
6. 线索到结论:一张可信度打分卡和复核的三个动作
材料齐了、冲突也列出来了,最后一步是把它们压成一个能被人拿去做决策的结论。我一般用一张五维打分卡,每维 0 到满分,加权算总分,但总分只作参考,真正决定结论的是有没有单维触底。
| 维度 | 权重 | 打分依据 | 触底条件 |
|---|---|---|---|
| 主体真实性 | 30 | 唯一主体标识、登记状态、经营范围是否覆盖标的业务 | 登记状态异常或经营范围完全不覆盖 |
| 履约能力证据 | 25 | 同类项目公开记录、中标公示、可核实的交付案例 | 一条 S1 级履约证据都没有 |
| 关联方风险 | 20 | 关联簇规模、共用注册邮箱或证书的域名数量 | 出现未披露的同邮箱控制主体 |
| 时间线自洽 | 15 | 成立、域名、证书、公告的先后关系 | 存在两处以上无法解释的倒挂 |
| 材料一致性 | 10 | 自述与公开登记的一致程度 | 关键字段(名称、地址、成立时间)不一致 |
任何一维触底,直接给“不建议”,不管总分多高。这个规则看起来粗暴,但它挡住了大部分后来出问题的项目——出问题的从来不是“各项都中等”的主体,而是某一项烂到底但被其他项平均掉的主体。
复核我固定做三个动作。换源复核:同一个结论,换一个完全不同类型的来源再确认一次,比如登记信息用另一个公开库交叉一遍。换人复核:让没参与采集的人只看证据表,看他能不能得出同样的结论,得不出就说明推断链条有断点。换时间复核:隔一周再跑一次域名和证书,看有没有新增的 SAN 或变更的名称服务器——如果有,说明对方在这段时间里动过基础设施,这本身就是信息。
最后说个我自己的习惯。每份报告交出去之前,我会单独写一页“我没查到的”,把 C 类字段和所有未解决的冲突项列清楚。刚开始觉得这是自曝其短,做了几次之后发现恰恰相反:委托方最认的往往就是这一页,因为它证明你知道边界在哪里。尽调这一行,能说清“我不知道什么”,比能说出“我知道什么”更值钱。
希望帮到你。
本文还有配套的精品资源,点击获取