bayufedra/ipti
GitHub: bayufedra/ipti
一款多源 IP 威胁情报评估工具,在将公网 IP 加入白名单前提供基于证据的可解释威胁评分与决策建议。
Stars: 35 | Forks: 4
# IPTI — IP 威胁情报
[](https://blackhatmea.com/speaker/bayu-fedra-abdullah)
**IPTI** 在考虑将公共 IP 地址列入组织内部网络的白名单**之前**,通过**基于证据、可解释**的评估对其进行分析:包括威胁评分、证据覆盖率指标、身份置信度指标以及暴露风险指标。
## 目录
1. [功能介绍](#what-it-does)
2. [安全限制](#security-limitations)
3. [提供商](#providers)
4. [自动启用提供商](#automatic-provider-enablement)
5. [安装说明](#installation)
6. [配置参考](#configuration-reference)
7. [CLI 用法](#cli-usage)
8. [Python 库用法](#python-library-usage)
9. [决策模型](#the-decision-model)
10. [地理与提供商策略](#geography--provider-policy)
11. [评分:威胁、身份、暴露、覆盖率](#scores)
12. [JSON 输出](#json-output)
13. [文本输出](#text-output)
14. [退出码](#exit-codes)
15. [缓存](#caching)
16. [错误处理](#error-handling)
17. [IPv4 和 IPv6 行为](#ipv4-and-ipv6-behavior)
18. [建议的白名单控制](#recommended-whitelist-controls)
19. [测试与质量](#testing--quality)
20. [从 v1 迁移](#migration-from-v1)
## 功能介绍
给定一个或多个公共 IP,IPTI 会:
- 并发查询多达三个**威胁情报**提供商,并进行故障隔离(一个失败绝不会中止其他查询)。
- *仅*从返回了可用证据的提供商中推导出**威胁评分**(0–100),并根据配置的权重 × 置信度进行动态归一化。
- 衡量**证据覆盖率**,以便让您了解实际响应的已配置情报比例。
- 通过**身份**(ASN/所有者/FCrDNS)和**暴露**(开放服务/CVE)上下文进行丰富——这可能会提高警惕,但**绝不能**抵消真实的威胁证据。
- 输出**决策**(`APPROVE_WITH_CONTROLS` / `MANUAL_REVIEW` / `DENY` / `INCOMPLETE` / `NOT_APPLICABLE`)、**威胁分类**、原因以及建议的白名单控制措施。
## 安全限制
- 威胁情报源存在覆盖盲区和报告延迟;没有证据并不代表没有威胁。
- 坚定的攻击者可能会使用任何情报源都尚未知晓的全新基础设施。
- 丰富信息(IPInfo/Shodan)属于时间点快照,可能已经过时。
- IPTI 不会自行扫描目标;它只查询第三方数据。
- 地理位置和托管提供商属于**策略上下文**,不能作为恶意行为的证明。
## 提供商
| 提供商 | 角色 | 环境变量键 | 文档 |
|---|---|---|---|
| **AbuseIPDB** | 威胁情报 | `ABUSEIPDB_API_KEY` | |
| **VirusTotal** | 威胁情报 | `VIRUSTOTAL_API_KEY` | |
| **AlienVault OTX** | 威胁情报 | `ALIENVAULT_API_KEY` | |
| IPInfo | 丰富信息(身份/地理) | `IPINFO_API_KEY` | |
| Shodan | 丰富信息(暴露面) | `SHODAN_API_KEY` | |
每个提供商都是**可选且独立的**。默认情况下,必须有两个威胁提供商成功响应才能避免出现 `INCOMPLETE` 结果(`IPTI_MIN_SUCCESSFUL_THREAT_SOURCES=2`)——如果您有意仅使用单个提供商运行,请将其设置为 `1`(或传递 `--min-sources 1`),这会触发降低置信度的警告。
### 隐私 / 匿名化检测(VPN / 代理 / Tor / 托管)
这些标志来自 IPInfo 的**隐私检测**,这是一个**付费附加功能**。
免费的/基础的 IPInfo token 仅返回地理位置信息——不包含 `privacy` 对象——因此这些标志会正确显示为 `unknown`(绝不会伪造为 `False`),并且 IPTI 会发出警告提示您这一点。**Tor 是个例外:**它也可以通过 **AbuseIPDB 的 `isTor`** 字段免费检测到,因此即使不升级 IPInfo,Tor 检测依然有效。每个解析出的标志都会显示其来源,例如 `Tor: True (via abuseipdb)`,完整的数据集位于 `enrichment.anonymization` 中。要获取 VPN/代理/中继/托管信息,请使用包含隐私检测的 IPInfo 套餐——一旦响应中包含该数据,IPTI 就会自动解析。
## 自动启用提供商
每个提供商都有一个 `IPTI__ENABLED` 标志,接受 `auto` / `true` / `false`:
| 模式 | 存在密钥 | 缺少密钥 |
|---|---|---|
| `auto`(默认) | **启用** | **禁用**(静默处理) |
| `true` | **启用** | **禁用**,并发出警告(绝不会崩溃) |
| `false` | **禁用** | **禁用** |
**已禁用**的提供商永远不会被查询,并且会被**排除**在 `platforms_queried`、`platforms_succeeded`、`platforms_failed`、评分分母和归一化权重之外。**已启用但失败**的提供商*不会*被视为是干净的——相反,它会降低证据覆盖率。
## 安装说明
要求 **Python 3.10+**。
```
git clone https://github.com/bayufedra/ipti
cd ipti
python -m venv .venv && source .venv/bin/activate
# 仅 runtime
pip install -r requirements.txt
# 或安装 package + console script + dev tools
pip install -e ".[dev]"
cp .env.example .env # then fill in the API keys you have
```
## 配置参考
配置在加载时会进行验证;无效值会快速报错并附带清晰的提示信息。有关完整的注释列表,请参见 [`.env.example`](.env.example)。关键设置:
| 变量 | 默认值 | 含义 |
|---|---|---|
| `IPTI__ENABLED` | `auto` | 每个提供商的 `auto`/`true`/`false` |
| `_API_KEY` | – | 提供商 API token |
| `IPTI_REQUEST_CONNECT_TIMEOUT` | `3.05` | 连接超时 (秒) |
| `IPTI_REQUEST_READ_TIMEOUT` | `15` | 读取超时 (秒) |
| `IPTI_REQUEST_RETRIES` | `2` | 瞬时故障重试次数 |
| `IPTI_MAX_CONCURRENCY` | `8` | 最大并发 IP 数 / 连接数 |
| `IPTI_CACHE_TTL_SECONDS` | `3600` | 成功证据的 TTL |
| `IPTI_ERROR_CACHE_TTL_SECONDS` | `60` | 错误的 TTL(必须 ≤ 成功的 TTL) |
| `IPTI_MIN_SUCCESSFUL_THREAT_SOURCES` | `2` | 低于此值 → `INCOMPLETE` |
| `IPTI_MIN_THREAT_COVERAGE` | `0.67` | 低于此值 → `MANUAL_REVIEW` |
| `IPTI_MAX_THREAT_AGE_DAYS` | `90` | 更旧的证据会被判定为 `STALE` |
| `IPTI_ABUSEIPDB_WEIGHT` | `0.35` | 原始权重(总和无需为 1.0) |
| `IPTI_VIRUSTOTAL_WEIGHT` | `0.40` | 原始权重 |
| `IPTI_ALIENVAULT_OTX_WEIGHT` | `0.25` | 原始权重 |
| `IPTI_THREAT_REVIEW_THRESHOLD` | `30` | 威胁评分 → 人工审核 |
| `IPTI_THREAT_DENY_THRESHOLD` | `70` | 威胁评分 → 拒绝 |
| `IPTI_EXPOSURE_REVIEW_THRESHOLD` | `60` | 暴露风险 → 人工审核 |
| `IPTI_IDENTITY_MINIMUM_CONFIDENCE` | `85` | 身份置信度下限(提供所有者时生效) |
| `IPTI_RESTRICTED_COUNTRIES` | – | 策略:强制拦截这些 ISO 代码(→ `DENY`) |
| `IPTI_REQUIRE_APPROVAL_COUNTRIES` | – | 策略:对这些 ISO 代码进行人工审核(→ `MANUAL_REVIEW`) |
| `IPTI_EXPECTED_COUNTRIES` | – | 策略:*不在此*列表中的任何国家/地区 → `MANUAL_REVIEW` |
| `IPTI_BLOCK_TOR` | `true` | 强制拦截确认的 Tor |
| `IPTI_BLOCK_OPEN_PROXY` | `true` | 强制拦截确认的开放/住宅代理 |
| `IPTI_ALLOW_NON_GLOBAL` | `false` | 允许私有/保留 IP |
权重**无需**总和为 1.0;它们会根据实际返回可用证据的提供商进行动态归一化。已禁用的提供商永远不会归一化到评分中。
## CLI 用法
```
# 单个 IP
ipti -i 8.8.8.8
# 多个 IP (IPv4 + IPv6),JSON 输出
ipti -i 1.1.1.1 2606:4700:4700::1111 -f json
# 从文件读取,保存报告,仅摘要
ipti -l ips.txt -o report.txt -S
# 覆盖 providers(CLI 优先于 env)并在 review 时 gate CI
ipti -i 203.0.113.5 --disable-provider shodan --fail-on-review
# 验证配置而不进行任何评估
ipti --config-check
```
实用标志:`--no-color`、`--verbose`、`--debug`、`--show-disabled`(在文本报告中单独列出已禁用的提供商)、`--fail-on-review`(在出现审核/不完整状态时以非零状态退出)、`--config-check`、`--version`、`--claimed-owner NAME`、`--allow-non-global`、`--enable-provider NAME` / `--disable-provider NAME`(可重复使用;CLI 会覆盖环境变量),以及阈值覆盖参数 `--max-age`、`--review-threshold`、`--deny-threshold`、`--min-sources`、`--min-coverage`。颜色仅在交互式终端中输出,并可通过 `--no-color`、`NO_COLOR`、JSON 输出或文件输出禁用。
(`python main.py ...` 依然可用,作为控制台脚本的轻量级垫片实现。)
## Python 库用法
### 异步引擎 — 单个与批处理(推荐)
```
import asyncio
from ipti import AssessmentEngine, load_settings
async def main():
settings = load_settings() # reads .env / environment
async with AssessmentEngine(settings) as engine:
a = await engine.assess("8.8.8.8", claimed_owner="Google")
print(a.decision.value, a.threat_classification.value)
print("threat", a.scores.threat, "coverage", a.scores.evidence_coverage)
# Batch: bounded concurrency, input order preserved, duplicates removed.
for r in await engine.assess_many(["1.1.1.1", "8.8.8.8", "1.1.1.1"]):
print(r.ip, r.decision.value)
asyncio.run(main())
```
`assess()` 和 `assess_many()` 返回 `Assessment` 对象。`decision` 和 `threat_classification` 是**枚举类型**——使用 `.value` 获取字符串格式。
结构化字段:`a.scores`(`threat`、`identity_confidence`、`exposure_risk`、`evidence_coverage`)、`a.providers`(`dict[str, ProviderResult]`)、`a.enrichment`(`ipinfo`/`shodan`/`ptr`/`exposure`/`identity`/`policy`/`anonymization`)、`a.hard_blocks`、`a.review_reasons`、`a.recommended_controls`、`a.warnings` 以及 `a.is_safe`(已弃用)。
### 一次性异步辅助函数
```
import asyncio
from ipti import assess_ip
assessment = asyncio.run(assess_ip("1.1.1.1"))
print(assessment.decision.value, assessment.scores.threat)
```
### 转换为 JSON 可序列化的字典
```
import asyncio
from ipti import assess_ip
from ipti.reporting import assessment_to_dict, render_json
a = asyncio.run(assess_ip("8.8.8.8"))
data = assessment_to_dict(a) # plain dict (what the CLI's -f json prints)
print(data["decision"], data["scores"]["threat"])
print(render_json(a)) # pretty JSON string
```
### 遗留同步 API(向后兼容)
`IPTI(ip).ipti_check()` 依然可用,并返回一个属于旧模式**超集**的字典。它会发出 `DeprecationWarning`;`is_safe`/`safe_ratio` 已弃用——建议使用 `decision` 和 `scores`。
```
from ipti import IPTI
result = IPTI(
"8.8.8.8",
max_age_in_days=60, # mapped onto IPTI_MAX_THREAT_AGE_DAYS
score_threshold=50, # mapped onto IPTI_THREAT_REVIEW_THRESHOLD
user_threshold=5, # accepted for signature compat; no longer used
safe_ratio=0.8, # accepted for signature compat; no longer used
).ipti_check()
print(result["ip"], result["decision"], result["is_safe"])
print("threat:", result["scores"]["threat"],
"coverage:", result["scores"]["evidence_coverage"])
```
## 决策模型
`decision` 是以下值之一:
| 决策 | 含义 |
|---|---|
| `APPROVE_WITH_CONTROLS` | 无硬性阻断;威胁低于审核阈值;覆盖率达标;应用建议的控制措施 |
| `MANUAL_REVIEW` | 存在可疑证据、覆盖率低、所需来源不可用/过时、暴露风险高、身份置信度低,或触发策略标记 |
| `DENY` | 触发硬性阻断、威胁 ≥ 拒绝阈值,或 ≥2 个相互印证的恶意来源 |
| `INCOMPLETE` | 成功响应的威胁来源数量少于要求——**绝不会被**判定为“安全” |
| `NOT_APPLICABLE` | 输入的 IP 不是有效的、全球可路由的 IP |
单独的 `threat_classification` 描述了证据本身:`NO_KNOWN_THREAT`、`LOW_RISK`、`SUSPICIOUS`、`HIGH_RISK`、`CONFIRMED_MALICIOUS`、`UNKNOWN`。
**硬性阻断**(→ `DENY`)包括:任何提供商给出高置信度的恶意结论、两个独立的恶意结论、恶意结论 + 独立的可疑印证、在策略拦截时确认使用了 Tor/开放代理、触发受限国家策略,或实质性的所有权不匹配。上下文元数据(PTR、地理位置提供商信誉、标准开放端口)可能会引发担忧或降低置信度,但**绝不能**降低已计算出的威胁评分或抵消直接的威胁证据。
## 地理与提供商策略
地理位置和托管提供商属于**策略上下文**,绝不是威胁证据。在 v1 中,“高风险国家”会在暗中降低安全评分,并将 CN/RU/OVH 等硬编码为高风险——这会导致误报,现已被移除。在 v2 中:
- 开箱即用时,没有任何国家或提供商被判定为高风险(所有策略列表均为空)。
- 您可以根据组织需求通过 `IPTI_RESTRICTED_COUNTRIES`(→ `DENY`)、`IPTI_REQUIRE_APPROVAL_COUNTRIES`(→ `MANUAL_REVIEW`)和 `IPTI_EXPECTED_COUNTRIES`(列表外的任何国家/地区 → `MANUAL_REVIEW`)进行选择性加入。
- 触发策略会将**决策**推向审核/拒绝——它**绝不会**改变威胁评分。结果是透明的(显示在*策略评估*部分和 `enrichment.policy` 中),而不是隐藏的数值惩罚。
```
# "仅 approve 这些国家/地区的 IP,deny 这些,review 其余部分"
IPTI_EXPECTED_COUNTRIES=US,GB,SG IPTI_RESTRICTED_COUNTRIES=KP ipti -i 203.0.113.5
```
## 评分
三个独立的测量值加上一个覆盖率数据——绝不会平均为一个容易产生误导的数字:
- **威胁(0–100)**——有多少证据表明存在当前/近期的恶意活动。经过置信度加权,并在提供可用证据的提供商中进行动态归一化。
- **身份置信度(0–100)**——该 IP 映射到其预期所有者/角色(ASN、组织、正向确认反向 DNS、可选的声明所有者)的置信程度。
- **暴露风险(0–100)**——主机暴露的程度(公共服务、未加密/管理/数据库端口、已知 CVE、代理/VPN/Tor)。当不存在 Shodan 记录时为 `null`(即**未知**,而非低风险)。
- **证据覆盖率(0.0–1.0)**——实际返回了可用证据的已配置启用权重。已禁用的提供商不会降低该值;而已失败的提供商会降低该值。
## JSON 输出
```
{
"schema_version": "2.0",
"ip": "8.8.8.8",
"ip_version": 4,
"checked_at": "2026-07-15T05:00:00+00:00",
"decision": "APPROVE_WITH_CONTROLS",
"threat_classification": "NO_KNOWN_THREAT",
"is_safe": true, // deprecated: == (decision APPROVE_WITH_CONTROLS)
"scores": {
"threat": 1.0,
"identity_confidence": 95.0,
"exposure_risk": 9.0,
"evidence_coverage": 1.0
},
"platform_summary": {
"platforms_available": 3, "platforms_enabled": 3, "platforms_disabled": 0,
"platforms_queried": 3, "platforms_succeeded": 3, "platforms_failed": 0
},
"providers": {
// Each provider object also carries: data_freshness_days, queried_at,
// observed_at, duration_ms, threat_categories, raw_metrics, error_code,
// error_message. Disabled providers appear with enabled=false and a
// "disabled_*" status (never omitted).
"abuseipdb": { "enabled": true, "status": "clean", "risk_score": 0.0,
"confidence": 0.95, "reasons": ["Abuse confidence score is 0"] },
"virustotal": { "enabled": true, "status": "clean", "risk_score": 0.0,
"confidence": 0.95, "reasons": ["0 engines flagged malicious ..."] },
"alienvault_otx": { "enabled": true, "status": "clean", "risk_score": 5.0,
"confidence": 0.70, "reasons": ["No active recent threat pulses"] }
},
"enrichment": {
"ipinfo": { "enabled": true, "status": "clean",
"data": { "country": "US", "city": "Mountain View",
"org": "AS15169 Google LLC", "asn": "AS15169" } },
"shodan": { "enabled": true, "status": "clean",
"data": { "ports": [53, 443] } },
"ptr": { "status": "organization_ptr", "ptr": "dns.google",
"forward_addresses": ["8.8.8.8"] },
"exposure": { "exposure_risk": 9.0, "risk_level": "low",
"open_ports": [53, 443], "critical_services": [], "cves": [] },
"identity": { "identity_confidence": 95.0, "network_type": "unknown",
"ptr_status": "organization_ptr" },
"policy": { "country": "US", "geographic_policy": "neutral",
"geographic_reason": "US is not on any policy list",
"provider_context": "AS15169 Google LLC" },
"anonymization": { "is_vpn": null, "is_proxy": null, "is_tor": null,
"is_relay": null, "is_hosting": null, "sources": {} }
},
"hard_blocks": [],
"review_reasons": [],
"recommended_controls": [
"Allow only the required destination host and port",
"Use a /32 IPv4 rule or /128 IPv6 rule",
"Require application-level authentication",
"Enable connection logging and alerting",
"Configure an expiration date and schedule periodic re-review"
],
"warnings": []
}
```
提供了 `schema_version`,以便未来的变更能够被安全管理。
## 文本输出
```
=== IP Threat Intelligence Assessment ===
IP Address : 8.8.8.8
IP Version : 4
Checked At : 2026-07-15T05:00:00+00:00
Schema Version : 2.0
Decision : APPROVE_WITH_CONTROLS
Threat Class : LOW_RISK
--- Scores ---
Threat Score : 1.0 / 100
Evidence Coverage : 100%
Identity Conf. : 95.0 / 100
Exposure Risk : 9.0 / 100
--- Threat Providers ---
Available 3 Enabled 3 Disabled 0 Queried 3 Succeeded 3 Failed 0
abuseipdb : clean risk=0 conf=0.95
- Abuse confidence score is 0
virustotal : clean risk=0 conf=0.95
alienvault_otx : clean risk=5 conf=0.70
--- Server Information ---
Country : US
City : Mountain View
Organization : AS15169 Google LLC
Hostname : dns.google
Privacy (unknown = provider plan does not report it, not False):
Proxy : unknown
VPN : unknown
Tor : unknown (or e.g. "True (via abuseipdb)")
Relay : unknown
Hosting : unknown
--- Policy Assessment ---
Geographic Policy : neutral — US is not on any policy list
Provider/Network : AS15169 Google LLC
--- Identity / Ownership ---
Identity Conf. : 95.0 / 100
Network Type : unknown
PTR Record : dns.google (organization_ptr)
--- Exposure / Ports ---
Exposure Risk : 9.0 / 100 (low)
Open Ports : 53, 443
--- Recommended Whitelist Controls ---
→ Allow only the required destination host and port
...
```
失败的提供商(`timeout`/`error`/`rate_limited`)始终会显示其状态;**已禁用**的提供商会在计数行中进行汇总,并且仅在指定 `--show-disabled` 时才会单独列出。
## 退出码
| 代码 | 含义 |
|---|---|
| `0` | 完成且无拒绝 |
| `1` | 一个或多个 IP 被**拒绝**(或在使用 `--fail-on-review` 时出现审核/不完整状态) |
| `2` | 一个或多个 IP 需要**人工审核 / 不完整 / 不适用** |
| `3` | 输入或配置无效 |
| `4` | 意外的内部故障 |
## 缓存
结果会在当前会话中按 `(provider, ip)` 进行缓存。成功的证据使用 `IPTI_CACHE_TTL_SECONDS`;操作错误使用较短的 `IPTI_ERROR_CACHE_TTL_SECONDS`;**身份验证失败绝不会被缓存**(以免之后被误判为干净)。批处理运行会去重相同的 IP 并复用连接池中的 HTTP 连接。缓存命中会在调试级别进行记录。
## 错误处理
- 单个提供商的失败会被转换为带有明确状态(`timeout`、`rate_limited`、`error`)的标准化结果——它绝不会中止其他提供商的查询。
- 瞬时故障(429/5xx、超时、连接重置)将使用指数退避 + 抖动进行重试,并遵守 `Retry-After`;4xx(429 除外)不会进行重试。
- 缺少 API 密钥绝不会导致 IPTI 崩溃;相关提供商仅会被禁用。
- API token 和授权头会从所有日志中脱敏处理。
## IPv4 和 IPv6 行为
两者均使用标准库的 `ipaddress` 模块进行验证。默认情况下,仅评估全球可路由的公共地址;私有/环回/链路本地/多播/保留/未指定/文档地址段将被拒绝(可使用 `--allow-non-global` / `IPTI_ALLOW_NON_GLOBAL` 进行覆盖)。AlienVault OTX 会自动使用 IPv4 或 IPv6 指标 endpoint。
采用 GPL-3.0-or-later 许可。欢迎在 参与贡献。
标签:GitHub, IP情报, Python, 威胁情报, 实时处理, 开发者工具, 文档结构分析, 无后门, 计算机取证, 逆向工具