
简介这是一套基于Python开发的威胁情报自动化播报系统面向网络安全从业者、SOC分析师及安全开发工程师解决多源威胁情报CVE等手工收集低效、信息分散、时效性差等问题。资源共70个文件包含25个核心Python脚本如main.py、crawler模块、DAO数据访问层、10个XML配置与模板、8个.dat格式情报源缓存文件、4个HTML前端播报页面以及SQL建表与回滚脚本、邮箱/微信通知配置、GitHub Actions工作流等整体压缩包仅738KB轻量易部署。已有191人学习下载项目结构清晰src目录分层明确支持无服务器方式一键ForkSecrets配置即用。读者可直接获得开箱即用的邮件推送方案、TOP10情报Web展示页、本地SQLite归档机制及完整CI/CD流程大幅降低威胁情报集成门槛。1. threat-intelligence 不是“情报截图合集”而是可嵌入检测 pipeline 的实时数据流它解决的是 SOC 工程师每天手动查 IOC 效率低、TTP 更新滞后、威胁上下文缺失这三类真实翻车现场你见过凌晨三点还在 Excel 里粘贴从 MISP 导出的 IOCs再逐条核对是否已在 SIEM 中命中你试过把一份 PDF 格式的 APT 组织技战术报告硬塞进 SOAR 的 playbook 触发条件里结果因字段不一致导致误报率飙升threat-intelligence 这个词在很多团队里还停留在“下载一个 IOC 列表 → 手动导入防火墙 → 等下次通报再重来”的黑匣子阶段。但真正能跑进生产环境的 threat-intelligence必须满足三个硬指标结构化JSON/STIX2、可编程Python SDK 或 REST API、可验证来源可信度时效性打分。本资源包不是 PDF 报告或静态 CSV而是一套开箱即用的 Python 工程化实践集合——包含 STIX2 解析器、MISP/OTX/AbuseIPDB 三源自动拉取脚本、IOC 去重与置信度融合逻辑、以及直接对接 Elasticsearch 和 Suricata 的输出适配器。适合正在搭建 SOAR 基础能力、或想把威胁情报从“人工查阅”升级为“自动决策输入”的蓝队工程师、SIEM 管理员和检测规则开发人员。它不教你怎么读 APT 报告只告诉你怎么让报告里的 TTP 自动变成 Suricata 规则里的flowbits。2. 构建可落地的威胁情报流水线从原始数据到可执行 IOC 的四步转化逻辑2.1 为什么必须放弃 CSV/Excel转向 STIX2 作为中间数据模型威胁情报的混乱根源从来不是数据少而是格式碎片化。MISP 导出的是 JSON但字段名是attribute_uuidOTX API 返回的是pulse结构含indicators数组但无kill_chain_phasesAbuseIPDB 只提供 IP分数连恶意类型都得靠正则猜。如果直接拼接这些原始响应你的 pipeline 会像一台用胶带粘合的发动机——某天 MISP 升级了 API 版本attribute_uuid变成object_id整个 IOC 同步就停摆。STIX2 是目前唯一被主流平台MISP、AlienVault、ThreatConnect原生支持的标准化模型它强制定义了Indicator、Malware、AttackPattern之间的关系并通过created_by_ref字段绑定可信来源。本资源包默认使用stix2Python 库v3.0.1构建统一解析层所有外部源数据在入库前必须转换为标准 STIX2 Bundle 对象。这不是为了“高大上”而是为了后续做Indicator与AttackPattern的关联查询时不用再写 20 行正则去匹配“T1059.001”和“PowerShell Execution”。# stix2_bundle_builder.py 示例统一入口函数 from stix2 import Bundle, Indicator, Malware, Relationship, ThreatActor def build_stix2_bundle(ioc_data: dict, source: str) - Bundle: ioc_data: 来自 MISP/OTX/AbuseIPDB 的原始响应字典 source: misp, otx, abuseipdb 返回包含 Indicator Relationship ThreatActor 的 Bundle 对象 # 步骤1提取核心 IOCIP/Domain/Hash if source misp: pattern f[ipv4-addr:value {ioc_data[value]}] indicator Indicator( idfindicator--{uuid4()}, patternpattern, pattern_typestix, valid_fromdatetime.now(timezone.utc), labels[malicious-activity] ) elif source otx: # OTX 的 pulse 包含多个 indicators需遍历 pattern f[file:hashes.SHA-256 {ioc_data[indicator]}] indicator Indicator( patternpattern, labels[malware], confidenceioc_data.get(confidence, 70) # OTX 提供置信度 ) else: # abuseipdb pattern f[ipv4-addr:value {ioc_data[ipAddress]}] indicator Indicator( patternpattern, labels[anomalous-traffic], confidenceint(ioc_data[abuseConfidenceScore]) # 直接映射为 0-100 分 ) # 步骤2绑定来源关键避免后续无法溯源 threat_actor ThreatActor( namef{source.upper()}_Feed, identity_classsystem ) relationship Relationship( relationship_typeidentifies, source_refthreat_actor.id, target_refindicator.id ) return Bundle(objects[threat_actor, indicator, relationship])提示stix2库的Bundle对象是内存对象不是文件。本包所有 STIX2 操作均在内存完成避免频繁序列化/反序列化带来的性能损耗。若需导出.json文件供其他系统消费调用bundle.serialize(prettyTrue)即可。2.2 三源自动拉取MISP、OTX、AbuseIPDB 的认证与限频实战配置MISP、OTX、AbuseIPDB 的 API 调用绝非简单requests.get()。每个平台都有自己的认证机制、速率限制策略和错误码语义。硬编码 token 或忽略429 Too Many Requests会导致 pipeline 在高峰时段静默失败——你以为数据同步正常其实过去 6 小时的 IOC 全丢了。本资源包采用tenacity库实现指数退避重试并为每个源单独封装认证类平台认证方式限频策略关键错误码处理MISPX-Auth-Token Header每分钟 100 请求可配403Token 失效触发告警OTXX-OTX-API-Key Header每分钟 12 次免费版404Pulse ID 不存在跳过AbuseIPDBKey Query Param每天 1000 次需注册401Key 无效停止该源同步# feed_puller.py 中的 MISP 拉取器带重试与限频 from tenacity import retry, stop_after_attempt, wait_exponential import requests class MISPPuller: def __init__(self, url: str, api_key: str, rate_limit_per_minute: int 100): self.url url.rstrip(/) self.session requests.Session() self.session.headers.update({ Authorization: api_key, Accept: application/json }) # 使用令牌桶算法控制速率简化版 self.rate_limiter RateLimiter(max_callsrate_limit_per_minute, period60) retry( stopstop_after_attempt(3), waitwait_exponential(multiplier1, min4, max10) ) def fetch_indicators(self, last_update: str) - list: last_update: ISO8601 时间字符串如 2024-05-20T00:00:00Z 返回MISP attributes 列表已过滤 type in [ip-dst, domain, md5, sha256] self.rate_limiter.call() # 触发限频检查 resp self.session.get( f{self.url}/attributes/restSearch, params{ limit: 500, page: 1, type: ip-dst,domain,md5,sha256, date_from: last_update, enforceWarninglist: False, includeContext: False } ) resp.raise_for_status() # 自动抛出 4xx/5xx 异常 data resp.json() if not data.get(response): raise ValueError(MISP response missing response key) return data[response].get(Attribute, []) # 使用示例 puller MISPPuller(https://your-misp.example.com, your-api-key-here) indicators puller.fetch_indicators(2024-05-20T00:00:00Z)注意RateLimiter类并非第三方库而是本包内置的轻量级实现基于time.time()和滑动窗口避免引入ratelimit等额外依赖。其核心逻辑是记录每秒请求数超限则time.sleep()。你可根据实际环境调整max_calls和period。2.3 IOC 去重与置信度融合为什么不能简单用set()去重两个不同来源上报同一个恶意 IP但置信度分别是 95%MISP和 30%AbuseIPDB你该信谁直接set()去重会丢失所有上下文导致高置信度情报被低置信度覆盖。本包采用加权置信度融合算法若同一 IOC相同 value same type出现在多个源取最高置信度若同一源多次上报如 MISP 中同一 IP 出现在不同事件中取最近一次的置信度若无显式置信度如 MISP 默认无 confidence 字段则按来源可信度赋默认值MISP85, OTX75, AbuseIPDB60。# ioc_fusion.py 核心逻辑 from collections import defaultdict from typing import Dict, List, Tuple def fuse_iocs(ioc_list: List[dict]) - List[dict]: ioc_list: [{value: 1.2.3.4, type: ipv4-addr, source: misp, confidence: 95}, ...] 返回去重后列表每个元素含 fused_confidence 字段 # 按 (value, type) 分组 grouped defaultdict(list) for ioc in ioc_list: key (ioc[value], ioc[type]) grouped[key].append(ioc) fused [] for key, iocs in grouped.items(): # 步骤1提取所有置信度无则用默认值 confidences [] for ioc in iocs: conf ioc.get(confidence) if conf is None: conf {misp: 85, otx: 75, abuseipdb: 60}.get(ioc[source], 50) confidences.append(conf) # 步骤2取最大值保守策略宁可信高不信低 fused_confidence max(confidences) # 步骤3保留首次出现的 source便于溯源 first_source iocs[0][source] fused.append({ value: key[0], type: key[1], fused_confidence: fused_confidence, sources: [ioc[source] for ioc in iocs], # 记录所有来源 first_seen_source: first_source }) return fused # 示例输入 raw_iocs [ {value: 1.2.3.4, type: ipv4-addr, source: misp, confidence: 95}, {value: 1.2.3.4, type: ipv4-addr, source: abuseipdb, confidence: 30}, {value: evil.com, type: domain, source: otx, confidence: 80} ] fused fuse_iocs(raw_iocs) # 输出: [{value: 1.2.3.4, type: ipv4-addr, fused_confidence: 95, ...}]提示fused_confidence不是平均值也不是加权和。实战中发现攻击者常利用低置信度情报如 AbuseIPDB 的扫描 IP污染高置信度源取最大值是最鲁棒的策略。你可以在fuse_iocs函数中轻松替换为其他策略如加权平均只需修改fused_confidence计算逻辑。3. 避坑威胁情报 pipeline 的五个血泪经验每一个都让我重启过三次服务3.1 现象MISP 同步任务每天凌晨卡死日志显示ConnectionResetError原因MISP 默认启用 Gzip 压缩但某些内网代理如 Squid未正确透传Content-Encoding: gzip头导致requests库解压失败。更隐蔽的是该错误不会立即抛出而是在resp.json()时才触发且堆栈指向json.decoder.JSONDecodeError完全误导排查方向。解决在 MISP Puller 初始化时强制禁用自动解压self.session.headers.update({Accept-Encoding: identity}) # 关键3.2 现象OTX Pulse 中的域名 IOC 同步后在 Elasticsearch 中搜索不到原因OTX 返回的indicator字段值为example[.]com用[.]替换.防止邮箱爬虫但 STIX2pattern要求严格语法[domain-name:value example[.]com]是非法 pattern。必须先还原为example.com。解决在build_stix2_bundle函数中对 domain 类型 IOC 增加预处理if ioc_data.get(type) domain: clean_value ioc_data[value].replace([.], .) # 还原点号 pattern f[domain-name:value {clean_value}]3.3 现象AbuseIPDB 拉取的 IP 在 Suricata 规则中匹配失败原因AbuseIPDB 的ipAddress字段是字符串但部分 IP 含前导零如010.002.003.004Suricata 的ip_proto匹配器不识别带零的格式必须标准化为10.2.3.4。解决使用ipaddress库强制标准化from ipaddress import ip_address try: normalized_ip str(ip_address(ioc_data[ipAddress])) except ValueError: # 非法 IP跳过 continue3.4 现象STIX2 Bundle 导出 JSON 后MISP 导入时报错Invalid property name created_by_ref原因MISP 的 STIX2 导入器要求created_by_ref必须指向一个已存在的 Identity 对象 ID而本包生成的ThreatActorID 是随机 UUIDMISP 中不存在对应 Identity。解决不创建ThreatActor改用Identity对象并固定 ID# 替换原 ThreatActor 创建逻辑 identity Identity( ididentity--threat-intel-pipeline, nameThreat Intel Pipeline, identity_classsystem ) # relationship.source_ref 改为 identity.id3.5 现象Elasticsearch 输出适配器写入速度慢CPU 占用 100%原因默认使用elasticsearch-py的单文档index()方法每条 IOC 发起一次 HTTP 请求。当每分钟同步 5000 IOC 时网络开销成为瓶颈。解决切换为批量写入bulk()并设置refreshFalsefrom elasticsearch import helpers actions [ { _op_type: index, _index: threat-iocs, _source: ioc_dict } for ioc_dict in fused_iocs ] helpers.bulk(es_client, actions, refreshFalse) # 关键关闭实时刷新4. 输出适配把 STIX2 Bundle 转成 Suricata 规则、Elasticsearch 文档、CSV 报表的三种落地姿势4.1 Suricata 规则生成器从 Indicator Pattern 到可部署规则的精准映射Suricata 不认识 STIX2 的pattern字段必须解析pattern字符串提取ipv4-addr:value、domain-name:value等字段并映射为ip_proto、http.host等关键字。本包提供suricata_rule_generator.py支持三种模式IOC TypeSTIX2 Pattern 示例生成的 Suricata Rule 示例触发动作ipv4-addr[ipv4-addr:value 1.2.3.4]alert ip any any - 1.2.3.4 any (msg:MISP IOC: Malicious IP; sid:1000001; rev:1;)alertdomain-name[domain-name:value evil.com]alert http any any - any any (msg:OTX IOC: Malicious Domain; content:evil.com; http.host; sid:1000002; rev:1;)alertfile:hashes[file:hashes.SHA-256 abc...]alert http any any - any any (msg:OTX IOC: Malware Hash; content:abc...# suricata_rule_generator.py 核心解析逻辑 import re def pattern_to_suricata_rule(pattern: str, confidence: int, source: str) - str: pattern: STIX2 pattern string, e.g., [ipv4-addr:value 1.2.3.4] confidence: fused_confidence (0-100) source: misp, otx, etc. 返回Suricata rule string # 步骤1提取 type 和 value match re.search(r\[(\w)-addr:value\s*\s*([^])\], pattern) if match: addr_type, ip_value match.groups() # 生成 IP 规则 action drop if confidence 80 else alert sid generate_sid(source) # 基于 source 生成唯一 sid return f{action} ip any any - {ip_value} any (msg:{source.upper()} IOC: Malicious IP; sid:{sid}; rev:1;) match re.search(r\[domain-name:value\s*\s*([^])\], pattern) if match: domain match.group(1) action drop if confidence 80 else alert sid generate_sid(source) return f{action} http any any - any any (msg:{source.upper()} IOC: Malicious Domain; content:{domain}; http.host; sid:{sid}; rev:1;) # 其他类型hash暂略... return # 使用示例 rule pattern_to_suricata_rule( [ipv4-addr:value 1.2.3.4], confidence95, sourcemisp ) # 输出: drop ip any any - 1.2.3.4 any (msg:MISP IOC: Malicious IP; sid:1000001; rev:1;)注意generate_sid()函数确保同一 source 的规则 sid 连续递增如 MISP 从 1000001 开始避免与现有规则冲突。sid 生成逻辑存储在sid_registry.json中每次调用后自动更新。4.2 Elasticsearch 输出适配器字段设计与索引模板最佳实践直接es.index()会创建动态 mapping导致confidence字段被识别为text而非integer后续聚合查询失败。本包提供预定义索引模板threat_iocs_template.json强制指定关键字段类型{ mappings: { properties: { value: { type: keyword }, type: { type: keyword }, fused_confidence: { type: integer }, sources: { type: keyword }, first_seen_source: { type: keyword }, created_at: { type: date } } } }# es_output_adapter.py 初始化逻辑 def init_threat_index(es_client: Elasticsearch, index_name: str threat-iocs): 创建索引并应用模板 if not es_client.indices.exists(indexindex_name): es_client.indices.create( indexindex_name, body{ settings: {number_of_shards: 3, number_of_replicas: 1}, mappings: { properties: { value: {type: keyword}, type: {type: keyword}, fused_confidence: {type: integer}, sources: {type: keyword}, first_seen_source: {type: keyword}, created_at: {type: date} } } } ) print(fIndex {index_name} created with custom mapping) # 写入逻辑使用 bulk def write_to_es(es_client: Elasticsearch, iocs: List[dict]): actions [] for ioc in iocs: action { _op_type: index, _index: threat-iocs, _source: { value: ioc[value], type: ioc[type], fused_confidence: ioc[fused_confidence], sources: ioc[sources], first_seen_source: ioc[first_seen_source], created_at: datetime.now(timezone.utc).isoformat() } } actions.append(action) helpers.bulk(es_client, actions, refreshFalse)提示refreshFalse是性能关键。生产环境建议每 500 条 IOC 批量提交一次并在每日凌晨执行POST /threat-iocs/_refresh保证搜索可见性。4.3 CSV 报表生成器给管理层看的“可读情报摘要”安全团队常被要求每周提交“威胁情报周报”但 raw IOC 列表毫无业务价值。本包csv_report_generator.py自动生成三张表表名字段用途ioc_summary.csvtype,count,avg_confidence,top_source情报类型分布与质量概览high_conf_iocs.csvvalue,type,fused_confidence,sources,first_seen_source80 分 IOC 清单供处置source_comparison.csvsource,total_iocs,unique_iocs,overlap_rate各源贡献度与重叠分析# csv_report_generator.py 核心逻辑 import pandas as pd def generate_csv_reports(fused_iocs: List[dict], output_dir: str): df pd.DataFrame(fused_iocs) # 表1汇总 summary df.groupby(type).agg( count(value, count), avg_confidence(fused_confidence, mean), top_source(first_seen_source, lambda x: x.mode().iloc[0] if not x.mode().empty else N/A) ).round(2).reset_index() summary.to_csv(f{output_dir}/ioc_summary.csv, indexFalse) # 表2高置信度 IOC high_conf df[df[fused_confidence] 80].sort_values( by[fused_confidence, value], ascending[False, True] ) high_conf.to_csv(f{output_dir}/high_conf_iocs.csv, indexFalse) # 表3源对比需先统计各源原始 IOC 数 # 此处省略统计逻辑实际代码中已实现 # source_stats.to_csv(f{output_dir}/source_comparison.csv, indexFalse)注意pandas仅用于报表生成不影响核心 pipeline。若环境禁止安装 pandascsv_report_generator.py提供纯csv模块备选实现性能稍低但零依赖。5. 验证 pipeline 健康度用三个命令快速诊断数据流是否“活”着5.1 实时监控用curl和jq检查 STIX2 Bundle 的完整性不要等告警才看 pipeline 是否存活。每天早会前执行这一行命令5 秒内确认核心数据流状态curl -s http://localhost:8000/api/v1/bundle?limit1 | jq .objects | length预期输出3一个 Bundle 至少含 Indicator Identity Relationship异常解读输出0API 未启动或数据库无数据输出1只有 Indicator缺少 Identity/Relationship →build_stix2_bundle逻辑故障输出nullAPI 返回非 JSON检查Content-Type: application/json提示本包内置 FastAPI 服务api_server.py端口8000路径/api/v1/bundle返回最新 Bundle。limit1参数确保只取最近一条避免大体积响应拖慢诊断。5.2 数据质量审计用stix2-validator检查 Bundle 是否符合规范STIX2 是强约束模型字段缺失或类型错误会导致下游系统如 MISP拒绝导入。stix2-validator是官方校验工具但直接运行stix2_validator bundle.json太重。本包提供轻量级校验函数# stix2_validator_lite.py from stix2 import parse def validate_bundle_json(bundle_json: str) - bool: bundle_json: STIX2 Bundle 的 JSON 字符串 返回True 表示有效False 表示无效打印具体错误 try: bundle parse(bundle_json, allow_customTrue) # 检查必要字段 assert hasattr(bundle, objects) and len(bundle.objects) 3, Bundle must have at least 3 objects assert all(hasattr(obj, id) for obj in bundle.objects), All objects must have id assert all(hasattr(obj, type) for obj in bundle.objects), All objects must have type return True except Exception as e: print(fSTIX2 validation failed: {e}) return False # 使用示例在 pipeline 最终步骤调用 with open(/path/to/latest_bundle.json, r) as f: bundle_json f.read() if not validate_bundle_json(bundle_json): send_alert(STIX2 Bundle validation failed!)注意parse(..., allow_customTrue)允许自定义扩展字段如x_threat_intel_pipeline_version避免因非标字段导致校验失败。5.3 溯源能力验证用 Elasticsearch 查询验证“这个 IOC 来自哪个源”情报的价值在于可追溯。执行以下查询应返回first_seen_source和sources字段curl -X GET http://localhost:9200/threat-iocs/_search \ -H Content-Type: application/json \ -d { query: { term: { value: 1.2.3.4 } }, fields: [first_seen_source, sources, fused_confidence] }预期响应hits: [{ fields: { first_seen_source: [misp], sources: [misp, abuseipdb], fused_confidence: [95] } }]翻车信号sources字段为空 →fuse_iocs未正确填充sources列表first_seen_source为null→fuse_iocs中iocs[0][source]访问越界空列表从那以后我每次上线新源比如新增 VirusTotal都强制走一遍这三步验证curl看 Bundle 长度是否达标validate_bundle_json()确认 STIX2 结构合法Elasticsearch 查询确认溯源字段完整。漏掉任何一步我都宁愿停服两小时也不让脏数据流入检测系统——因为修复一条错误 IOC 的成本是预防它的十倍。希望帮到你。本文还有配套的精品资源点击获取