DNS 劫持检测接口接入教程:domain 参数、请求示例与风险分级解读
排查“域名解析异常”“被运营商插广告”这类问题时,光查一条 DNS 记录不够,需要对比多个解析源的返回结果。DNS 劫持检测接口通过多家公共 DoH 并发查询同一域名并比对结果,给出风险分级。本文从第一次调用讲起。
一、适用场景
- 解析异常排查:用户反馈域名打不开,判断是本地污染还是服务端问题。
- 安全巡检:对关键域名做定期检测,发现解析被篡改的迹象。
- CDN 与 GeoDNS 核查:查看是否存在异常地域分流。
- 终端 hosts 排查:对比公共 DoH 结果,辅助判断本机解析是否被改。
需要明确:接口给出的是“基于多源对比的风险判断”,不是对劫持行为的取证结论,业务上应作为排查线索而非最终裁决。
二、接口能力边界
| 项目 | 说明 |
|---|---|
| 请求方法 | GET |
| 参数 | domain |
| 查询源 | 4 家公共 DoH 并发查询 |
| 记录类型 | A + AAAA 同时查询(IPv4 / IPv6) |
| 附加信息 | CNAME 链路、TTL 汇总 |
| 风险分级 | safe / safe_with_geodns / suspicious / hijack_likely / unknown |
| 输入兼容 | domain、https://domain、domain:port、domain/path 自动剥离 |
参与对比的 DoH 服务器:
| 来源 | 说明 |
|---|---|
| AliDNS | 阿里云,中国 |
| DNSPod | 腾讯,中国 |
| 360 安全 DNS | 中国 |
| Cloudflare | 美国,在中国大陆服务器侧可能超时,会被标记为 timeout |
关键点:Cloudflare 出现timeout是已知现象,单看这一项超时不能直接判定为风险,要结合其余三家的结果与整体分级来看。
三、请求参数与鉴权
参数只有一个:
| 参数 | 说明 |
|---|---|
domain | 待检测域名,支持多种输入格式,自动剥离协议、端口与路径 |
鉴权方式与请求头字段,素材未给出,以文档为准。这是GET请求,参数放在查询字符串中,接入前请确认是否需要携带 Key。
四、最小请求示例
下面是一个可复制的 curl 模板:
curl -X GET 'https://v1.apizero.cn/api/dns-check?domain=example.com'带协议头的输入也能直接传,接口会自动剥离:
curl -X GET 'https://v1.apizero.cn/api/dns-check?domain=https://example.com'对应的 Python 调用示例:
import requests def check_dns_hijack(domain: str, api_key: str | None = None) -> dict: headers = {} if api_key: headers["Authorization"] = f"Bearer {api_key}" resp = requests.get( "https://v1.apizero.cn/api/dns-check", params={"domain": domain}, headers=headers, timeout=20, # 多源并发查询,超时给宽一些 ) resp.raise_for_status() return resp.json()超时建议比普通查询放宽,因为接口内部要并发等待多家 DoH 返回。
五、返回值解读
风险分级是核心结果,五档含义如下(具体判定规则以文档为准):
| 分级 | 含义理解 |
|---|---|
safe | 多家结果一致,未发现异常 |
safe_with_geodns | 结果差异可由正常 GeoDNS 分流解释 |
suspicious | 存在可疑差异,需人工复核 |
hijack_likely | 多源结果显著不一致,疑似劫持 |
unknown | 数据不足或查询源多数失败,无法判断 |
除分级外,还应关注 A/AAAA 记录、CNAME 链路与 TTL 汇总。CNAME 链路能看出是否经过 CDN 跳转,TTL 能辅助判断缓存影响。
一个防御式解析示例(字段名请按文档实际返回替换):
LEVELS = {"safe", "safe_with_geodns", "suspicious", "hijack_likely", "unknown"} def parse_check(payload: dict) -> dict: level = payload.get("level") or payload.get("risk") # 字段名以文档为准 if level not in LEVELS: level = "unknown" sources = payload.get("sources") or {} timeouts = [name for name, info in sources.items() if isinstance(info, dict) and info.get("status") == "timeout"] return { "level": level, "timeout_sources": timeouts, "need_manual_review": level in ("suspicious", "hijack_likely"), }处理逻辑建议:suspicious与hijack_likely进入人工复核队列;unknown先重试一次再判定;safe_with_geodns不必告警。
六、常见错误与排查
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 参数错误 | domain为空或格式非法 | 传标准域名,其余格式由接口剥离 |
| 超时 | 查询源响应慢,Cloudflare 侧可能超时 | 放宽超时,看是否为单源超时 |
| 结果为 unknown | 多数查询源失败或数据不足 | 稍后重试,检查域名是否可解析 |
| 分级与预期不符 | GeoDNS 正常分流被误判 | 结合 CNAME 与 A 记录人工复核 |
| 鉴权失败 | 需要 Key 但未携带 | 以文档为准补充请求头 |
注意:单个来源timeout属于正常现象,不要把它当成劫持证据,也不要在这种情况下直接告警。
七、工程化注意事项
- 超时设置:多源并发查询耗时更长,客户端超时应显著大于单次 DNS 查询。
- 分级映射:本地维护五级枚举,未知值统一归入
unknown,避免展示层崩溃。 - 告警策略:只对
hijack_likely直接告警,suspicious走人工复核,unknown重试后再定。 - 来源记录:保存各 DoH 的返回与超时情况,便于复盘误判。
- 巡检频率:域名安全巡检按小时或按天做即可,不宜高频调用。
- 结果展示:把 CNAME 链路和 TTL 一并展示给排查人员,比只给一个分级更有用。
参考文档
- 接口文档