☰
Python自动化攻击图生成器:从拓扑到攻击路径的可视化分析
2026/10/5 6:19:58 网站建设 项目流程

简介:面向安全分析与攻防演练场景的自动化攻击图生成器源码,以Python为核心并集成Shell脚本,可帮助安全分析师将分散的漏洞与网络拓扑信息自动转化为直观的攻击路径图,降低人工构造攻击图的技术门槛。资源共101个文件,压缩包约37.75MB,文件类型涵盖JSON/YAML配置、DOT图形描述、PDF报告输出、Python源码与字节码、Shell部署脚本以及Git版本控制配置等,从配置解析到图形生成、报告导出均有覆盖,目录结构较完整,便于按模块阅读与二次开发。目前已有321人学习下载。通过源码可以了解攻击图构建的完整流程,包括网络拓扑解析、攻击路径枚举与可视化输出逻辑,同时可复用其中的配置模板、DOT渲染与Shell自动化部署脚本,为评估系统安全性、规划防御策略提供一套可落地的工具链。

1. 自动化攻击图生成器:安全分析师手里的一张“攻击者路线图”

用 attack-graph-generator 这套基于 Python 的自动化攻击图生成器做安全分析,最大的感受是:过去手工列攻击路径要花一两天的事,现在把拓扑数据喂进去,几分钟就能拿到一张可视化的攻击图。它的核心价值不是画图,而是帮你把“攻击者可能怎么打进来”这件事,从散落在漏洞报告里的文字描述,变成一张能直接看出路径依赖关系的图。这套项目适合三类人:一是做渗透测试但每次都要重复整理攻击链的工程师,二是做安全运维、需要定期评估内网风险的一线人员,三是刚接触攻击图概念、想找一个完整项目来读源码的学习者。项目本身用 Python 写主体,混了 Shell 脚本做自动化部署,压缩包里能看到 JSON、YAML、DOT、PDF、Python 源码甚至字节码文件,总共 100 个文件——这种文件构成本身就说明它不是一个玩具项目,而是一个有配置管理、有版本控制、有产物输出的完整方案。下面我从项目结构开始,把这份资源掰开讲清楚。

2. 项目结构拆解:100 个文件里哪些是核心,哪些只是产物

2.1 按文件类型把 100 个文件分成五类:配置、源码、图形、报告、辅助

拿到压缩包先别急着跑,第一步是搞清楚 100 个文件各自是什么角色。从文件类型看,这个项目的构成非常典型:JSON 和 YAML 是配置入口,DOT 是图形描述,PDF 是输出报告,Python 源码是逻辑主体,字节码是编译产物,ZIP 是打包归档,Shell 脚本是自动化调度的胶水层。

我建议先按扩展名做一次分类统计,再决定从哪里开始读。多数攻击图生成器的代码量没有想象中大,真正的核心逻辑往往集中在少数几个 Python 文件里,其余大量 DOT 文件是测试用例的产物或者模板。

#!/bin/bash # 解压后统计各类文件数量,明确项目构成 unzip attack-graph-generator.zip -d attack-graph-generator cd attack-graph-generator echo "=== 文件类型统计 ===" find . -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn echo "=== Python 源码文件列表 ===" find . -name "*.py" -type f | sort

这里的 find 和 sed 组合是做代码审计前最实用的热身动作:第一段命令统计出每种扩展名的文件数,让你一眼看出这个项目到底是 Python 为主还是配置为主;第二段命令把源码文件单独列出来,排除 DOT、PDF 这类产物的干扰。实际跑的时候注意 unzip 的路径别搞错,解压后立刻做一次文件统计,能避免后面反复在目录里迷路。

2.2 topology_graph.dot 与 attack_graph.dot:输入与输出的核心差异

压缩包里反复出现两个 DOT 文件:topology_graph.dot 和 attack_graph.dot。这两个文件名就是整个系统的输入输出关系——前者描述网络拓扑,后者是生成结果。区分清楚这两个文件,比读任何文档都更能理解项目架构。

从安全分析的角度看,拓扑图回答的问题是“网络里有什么”,攻击图回答的问题是“攻击者能怎么打”。topology_graph.dot 描述的是主机、防火墙规则、网络分段、开放端口这些基础事实;attack_graph.dot 在这个基础上叠加了漏洞利用关系,把“主机 A 能访问主机 B”和“主机 B 存在 CVE-2020-1234”组合成一条攻击路径。

这个项目把两者分开存放是很讲究的做法。在实际的攻防评估里,拓扑数据往往来自资产管理系统,攻击图则是动态变化的——同一条路径,可能因为一个补丁的部署就失效了。把输入和输出分离,意味着你只需要维护拓扑数据,重新跑一遍生成逻辑就能得到最新的攻击图,不需要手工去改攻击图。

2.3 .gitattributes 与 .gitignore:版本控制层面已经替你考虑好了

压缩包里有 .gitattributes 和 .gitignore,这两个文件容易被忽略,但它们透露了项目的版本管理方式。.gitattributes 里一般会定义 DOT、PDF 这类二进制或生成文件的 diff 处理方式,.gitignore 则把编译产物、临时文件和本地的输出目录排除在版本控制之外。

常见做法是把生成的 DOT 文件、PDF 报告、Python 字节码(pycache下的 .pyc)都写进 .gitignore。这意味着你在代码仓库里看到的 attack_graph.dot 可能不是实时生成的、而是某个版本的快照,真正的生成逻辑在 Python 源码里。如果你想把这套方案集成到自己的项目里,这两个 Git 配置文件是现成的模板,直接复制过去就能避免把产物误提交进仓库。

3. 从拓扑图到攻击图:生成逻辑与图论基础

3.1 攻击图的基本模型:状态节点、攻击动作与前提条件

读攻击图生成器的源码,绕不开图模型。攻击图在学术界的标准定义是一个有向图,节点表示系统状态,边表示攻击动作。一条从初始状态到最终状态的路径,就是一次完整的攻击链。

这个项目处理的核心是三类节点:初始状态节点(攻击者已具备的访问权限和位置)、中间状态节点(通过某个步骤获得的中间能力)、目标状态节点(比如拿到关键主机的 root 权限)。边代表动作,每条边至少带着两个条件:前提条件(当前必须满足什么状态)和后置条件(动作完成后新增什么状态)。

# 攻击图边结构的核心抽象(常见实现方式) class AttackEdge: def __init__(self, edge_id, source, target, action, preconditions, postconditions): self.edge_id = edge_id # 边的唯一标识 self.source = source # 起点状态节点 ID self.target = target # 终点状态节点 ID self.action = action # 攻击动作名称,如 "exploit_cve_2020_1234" self.preconditions = preconditions # 前提条件集合,必须全部满足 self.postconditions = postconditions # 后置条件集合,动作完成后新增 def is_applicable(self, current_states): """判断当前状态集合是否满足所有前提条件""" return all(pre in current_states for pre in self.preconditions)

这段代码体现的是攻击图生成中最核心的判定逻辑:一条攻击边能不能被激活,取决于当前状态集合是否包含它的全部前提条件。py 文件里如果有类似的类定义,那整个生成过程就是在反复执行“遍历边 → 检查前提 → 更新状态集合”的循环。实际项目里可能不是用类而是用字典实现,但思路完全一致。注意 preconditions 的类型一定要是集合或者列表,否则 in 判断的效率会很难看。

3.2 自动化推导流程:从拓扑数据到完整攻击路径

自动化的精髓在于不用人工指定攻击路径,而是由程序自己推导。典型流程分四步:解析拓扑结构、加载漏洞信息、初始状态设定、反复推导直到没有新状态产生。

项目里的 Python 源码大概率是这样一个骨架:先用一个解析器读 topology_graph.dot 拿到主机清单和连通关系,再从一个漏洞库(可能是 JSON 或者 YAML)读取每台主机上存在的漏洞,然后初始化攻击者的控制节点,最后循环扫描所有可能被利用的漏洞,把满足前提条件的利用动作变成一条攻击边,再把后置条件加进状态集合,直到状态集合不再增长。

# 攻击图自动推导逻辑(简化版) def generate_attack_graph(topology, vulnerability_db, initial_state): states = set(initial_state) # 当前所有已成立的状态 edges = [] # 已生成的所有攻击边 changed = True # 本轮是否有新增状态 while changed: changed = False for host in topology.hosts: for vuln in vulnerability_db.get_vulnerabilities(host): edge = build_attack_edge(host, vuln) if edge.is_applicable(states) and edge.id not in (e.id for e in edges): edges.append(edge) states.update(edge.postconditions) # 新增后置条件 changed = True # 状态有增长,继续下一轮 return AttackGraph(states=states, edges=edges)

这里用了一个类似 BFS 的固定点迭代:每轮循环遍历所有主机和漏洞,只要存在一条满足前提条件的攻击边就加入攻击图,并把后置条件并入状态集合,直到没有任何新状态产生。这个循环必然收敛,因为状态集合是有限的。参数上注意 initial_state 需要包含攻击者最初的立足点,比如“外网可达”或”已控制主机 A”,否则推导出来的攻击图可能为空。vulnerability_db 的查询接口如果按主机名过滤,那么在 topology 里主机命名必须和漏洞库里的命名规则一致。

3.3 为什么选 DOT 格式而不直接输出 PNG 或 PDF

DOT 是 Graphviz 的图形描述语言,纯文本、易解析、可版本控制。从工程角度选 DOT 是聪明的:PNG 是二进制没法 diff,PDF 不方便二次处理,而 DOT 文本可以写进测试用例做断言、可以转成 JSON 供其他系统消费、可以在出图前手工改节点颜色。项目里同时出现 DOT、PDF、JSON 三种格式,说明它的输出链路是“DOT 中间产物 → 其他格式最终产物”的渐进式管线。

这也解释了为什么压缩包里那么多 DOT 文件——它们既是输入也是中间产物。如果你要改这个项目,第一个动手点就是在 DOT 解析和生成这两段:解析拓扑用 pygraphviz 或 pydot,生成攻击图用字符串模板拼接然后交给 Graphviz 渲染。注意 Graphviz 的安装是硬依赖,没有它只能拿到 .dot 文本,连 PDF 报告都别想生成。

4. 本地复现与使用:环境准备、依赖与运行方式

4.1 环境准备:Python 版本、Graphviz 与依赖安装

动手跑之前先把环境装好。这类项目一般依赖 PyGraphviz、NetworkX 和 PyYAML,这三件套分别负责 DOT 解析、图算法和配置读取。我建议直接用虚拟环境隔离,别把依赖装进系统 Python,否则后面想换版本会很难受。

#!/bin/bash # 环境准备全流程 python3 -m venv attack-env source attack-env/bin/activate pip install --upgrade pip pip install pygraphviz networkx pyyaml pydot # Graphviz 本体是系统级依赖,pip 装不了 # Debian/Ubuntu 用 apt,macOS 用 brew,Windows 用 choco sudo apt-get install -y graphviz graphviz-dev

逻辑说明:前两行创建并激活虚拟环境,隔离依赖避免污染系统 Python;pip 安装四个 Python 包,其中 pygraphviz 负责 DOT 解析和生成,networkx 提供图结构算法基础,pyyaml 读取 YAML 配置,pydot 是纯 Python 的 DOT 操作替代方案。最后一行用 apt 安装 Graphviz 本体和它的开发头文件——pygraphviz 编译时依赖 graphviz-dev,缺了它安装直接报错。参数上建议锁定版本:pygraphviz 1.11 和 networkx 2.8 在多数场景下兼容性最好,新版 networkx 3.x 对老代码可能会有接口变动。

安装完成后用 python -c "import pygraphviz" 验证,如果报错说 libgvc.so 找不到,就是系统级依赖没装全。这一步翻了车别慌,去检查 graphviz-dev 有没有装上。

4.2 运行参数怎么设:从命令行到配置文件的一整套入口

项目的运行入口一般有两个层次:命令行参数控制单次运行的临时行为,YAML 配置文件控制长期稳定的策略参数。先看命令行:

#!/bin/bash # 典型调用方式(具体参数以 readme.txt 为准) python generator.py \ --topology topology_graph.dot \ --vuln-db vulnerabilities.yaml \ --output attack_graph.dot \ --format dot,pdf \ --max-depth 5

参数含义:--topology 指定输入拓扑文件;--vuln-db 指向漏洞数据库,通常是 YAML 格式,里面按主机名罗列 CVE 编号和利用条件;--output 指定攻击图的输出路径;--format 控制输出格式,这里同时要 dot 和 pdf 两种;--max-depth 限制攻击路径的最大深度,防止状态爆炸。

实际使用建议把参数固化到配置文件里,比如 config.yaml,里面包含主机清单、信任关系、漏洞列表这些相对稳定的信息。命令行参数适合调试时临时改,配置文件才是日常使用的正确姿势。--max-depth 这个参数要小心:设得太小会漏掉长攻击链,设得太大推导时间会指数级增长,一般从 3 开始试,观察输出结果再逐步调大。

4.3 输出产物解读:DOT 源文件、PDF 报告与属性文件的分工

跑完一次生成后,output 目录里通常会有几个关键文件。attack_graph.dot 是可以继续二次处理的图形源文件,适合交给脚本做分析;attack_graph.pdf 是给人和汇报用的最终报告;还有一些 .properties 或 .json 文件记录生成过程的参数和统计信息。

#!/bin/bash # 检查生成结果的完整性 ls -lh output/ echo "=== 攻击图 DOT 文件行数 ===" wc -l attack_graph.dot echo "=== 节点与边统计(粗筛) ===" grep -c "->" attack_graph.dot && echo "边数统计完成"

检查输出时重点看两个指标:攻击图有没有实际的边、节点命名是否符合预期。如果 wc -l 只有几行,说明攻击图几乎是空的——大概率是漏洞库加载失败或者初始状态配置错误。grep 统计 -> 出现的次数能快速估算边的数量级,攻击图里如果连一条利用关系的边都没有,那 PDF 报告再好看也没有分析价值。

4.4 属性文件的隐藏作用:运行参数回溯与复现保障

项目里出现 .properties 属性文件,这个细节容易被忽视。属性文件通常记录的是非结构化信息:生成时间、Python 版本、依赖版本、输入文件的哈希值。它的最大价值是回溯——两周后你拿到一张攻击图,想知道它是用什么版本的漏洞库、什么参数生成的,看属性文件就行。这在安全评估报告里尤其重要,因为结论必须可追溯、可复现。如果你把这份资源集成到自己公司内部的安全流程里,生成属性文件这件事一定要保留,别为了省事删掉。

5. 避坑指南:五个真实踩过的坑与对应排查方案

5.1 现象:DOT 文件生成了,但 PDF 渲染失败

有一次我跑完生成流程,attack_graph.dot 正常输出,但 PDF 文件没有生成。排查后发现是 Graphviz 的 dot 命令不在 PATH 里——Python 进程调用系统命令时找不到可执行文件。

原因:Graphviz 装好了,但安装路径没有被注册到环境变量;或者用的是 conda 环境,conda 装的 Graphviz 只在 conda 环境内可见。

解决:在运行脚本前手动验证 dot 命令可用,并把路径写进脚本开头。

#!/bin/bash # 验证 dot 命令是否可用 which dot || echo "dot not found in PATH" # Dot 不在 PATH 时显式指定路径(示例为 /usr/bin/dot) export PATH="/usr/bin:$PATH" python generator.py --topology topology_graph.dot

5.2 现象:attack_graph.dot 生成出来是空的,里面只有头没有边

遇到这种情况,先别怀疑代码有 bug。攻击图为空通常不是遍历逻辑坏了,而是初始状态没设置对。攻击者的起点如果不在拓扑图的任何主机上,推导逻辑从一开始就没有可激活的边。

原因:拓扑图里的主机名与漏洞库中的主机名对不上;或者初始状态节点写错,比如把攻击者起点写成了一个拓扑中不存在的主机 ID。

解决:去检查 topology_graph.dot 里主机的准确命名,再打开漏洞库确认引用名完全一致。这个坑是字符串大小写不敏感导致的,把两边统一成小写后再跑一次。调试时可以加一个 debug 参数,把每轮的 states 集合打印出来,看到底是第一步就走不下去,还是走了几步断了。

5.3 现象:项目里出现 .pyc 字节码文件,担心源码缺失

压缩包里有 Python 字节码(.pyc 或pycache目录),这是运行过之后留下的编译缓存,不是额外功能。新手容易把 .pyc 当成另一种源码,想从里面反编译出逻辑来读——完全没必要。

原因:Python 解释器把 .py 源码编译成字节码缓存,加速下次运行;压缩打包时未排除pycache目录,所以被带了进来。

解决:直接忽略所有 .pyc 文件,读 .py 源码就够了。如果想清理干净再看项目结构,用 find . -name "*.pyc" -delete 删掉即可。在版本控制里,确保 .gitignore 能过滤掉它们,避免以后入库。

5.4 现象:Windows 下路径分隔符导致拓扑文件加载失败

如果直接在 Windows 上跑,最常出问题的反而不是 Python 代码本身,而是文件路径。Shell 脚本里的路径写法是 Unix 风格(用斜杠分隔),Windows 的命令行工具不一定认。

原因:项目里的 Shell 脚本按 POSIX 环境写出,Windows 的 cmd 和 PowerShell 对引号和路径的处理规则不一样;配置文件里如果用了硬编码路径,跨平台直接翻车。

解决:一套省事的做法是全部改用 Python 脚本调用,绕开 Shell 脚本;另一套做法是把路径统一改成相对路径,由脚本入口文件动态获取当前目录。如果非要保留 Shell 脚本,在 Windows 上就用 Git Bash 而不是 cmd 来跑。

5.5 现象:System 属性文件内容无法解析,程序中断

压缩包里那个 System 文件,既不是源码也不是配置,而是一个带特殊属性的文件,比如来自 macOS 的 extended attributes 归档文件,或者在 Windows 下被标记为系统文件的条目。用普通文本编辑器打开可能看到乱码,程序尝试解析它就炸了。

原因:项目的辅助文件包含平台特定的元数据,Python 代码不会主动去读它,但如果你扫描目录时把所有文件都当成配置或者数据来加载,就会命中它报错。

解决:在加载配置和漏洞库时按扩展名做白名单过滤,只处理 .yaml、.json、.properties 和 .dot 文件,其他一律跳过。这一条做个简单的文件类型过滤就行。

# 按扩展名过滤有效配置文件,防止误读 System 之类杂项文件 import pathlib DATA_EXTS = {'.yaml', '.yml', '.json', '.properties', '.dot'} def load_config_files(directory): cfg_files = [] for p in pathlib.Path(directory).iterdir(): if p.suffix.lower() in DATA_EXTS: cfg_files.append(p) print(f"加载配置: {p.name}") return cfg_files

这段代码的核心价值是白名单机制:只认明确要处理的扩展名,其余文件一律不碰。有人喜欢用黑名单过滤 System 文件,但黑名单永远追不上新出现的杂项文件,白名单从根上规避了这类问题。

6. 进阶:把攻击图生成接入日常安全评估流程

工具跑通只是开始,真正让 attack-graph-generator 发挥价值的是把它接进你现有的安全评估流程。我个人习惯的做法是维护一套固定的评估节奏:每次拿到新的资产清单或漏洞扫描结果,就更新 topology_graph.dot 和漏洞库,然后重新生成攻击图,和上一版做对比,看攻击路径发生的变化。

先说批量处理的问题。单个拓扑文件用命令行参数跑没问题,但如果内网有几十个独立的网络分段,每个分段都要生成独立的攻击图,手动反复执行就太低效了。我一般会写一个调度脚本,循环读取一个目录下的所有拓扑文件,批量生成对应的攻击图,并输出一个汇总的 JSON 文件。

# 批量生成多个网络分段的攻击图并输出 JSON 摘要 import subprocess import json import pathlib topo_root = pathlib.Path("topologies") output_summary = [] for topo_file in sorted(topo_root.glob("*.dot")): # segment_name 从文件名推导,例如 "dmz.dot" -> "dmz" segment = topo_file.stem out_dot = pathlib.Path("output") / f"{segment}_attack.dot" out_pdf = pathlib.Path("output") / f"{segment}_attack.pdf" cmd = [ "python", "generator.py", "--topology", str(topo_file), "--output", str(out_dot), "--format", "dot,pdf" ] result = subprocess.run(cmd, capture_output=True, text=True) entry = { "segment": segment, "status": "ok" if result.returncode == 0 else "failed", "node_count": count_nodes(out_dot) if out_dot.exists() else 0, "edge_count": count_edges(out_dot) if out_dot.exists() else 0, "stderr": result.stderr[-200:] if result.returncode != 0 else "" } output_summary.append(entry) print(f"{segment}: {entry['status']} nodes={entry['node_count']} edges={entry['edge_count']}") # 汇总结果写入 summary.json,方便后续在报表中直接引用 with open("output/summary.json", "w", encoding="utf-8") as f: json.dump(output_summary, f, ensure_ascii=False, indent=2)

这段脚本把攻击图生成和批量管理串起来了:遍历 topologies 目录下的每个 .dot 拓扑文件,调用 generator.py 生成对应的攻击图和 PDF,再把结果汇总成一个 JSON 摘要。注意 subprocess.run 里的 capture_output 一定要保留,这样生成失败时能拿到 stderr 的输出做排查,否则几十个分段里有一个跑挂了,你都不知道挂在哪。count_nodes 和 count_edges 是两个需要自己实现的辅助函数,思路和前面 grep 命令一样——在生成的 DOT 里数节点标识和 -> 边的数量。

批量跑完再谈产出质量验证。攻击图的正确性不能靠肉眼扫,尤其是分段多的时候。我一般会挑几个关键路径做断言:指定一个源主机和一个目标主机,在生成的攻击图上跑最短路径算法,验证是否存在一条可达路径。如果拓扑和漏洞库明明包含该路径,但生成的攻击图里找不到,说明生成逻辑有遗漏,或者漏洞库里的前提条件和拓扑数据不自洽。

最后说一个这半年养成的习惯:很多项目的自动生成逻辑看着像黑匣子,跑完拿到结果就算完事。但攻击图这种安全产物,生成完一定要做一次交叉验证。我的做法是随机抽取 5% 的攻击边,手工检查对应漏洞的 CVSS 分数和利用条件是否匹配。这样做的目的不是怀疑代码,而是确认输入数据的质量没有问题——漏洞库里的历史遗留条目、运维改了端口没更新拓扑这些事,都会让生成结果变得不可信。从那以后我每次生成攻击图都强制走一遍流程:先跑批量脚本,再对比上一版差异,最后做抽样验证。这套组合拳打下来,攻击图作为安全决策依据的可靠性才真正立得住。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询