
简介面向安全分析与攻防演练场景的自动化攻击图生成器源码以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(statesstates, edgesedges)这里用了一个类似 BFS 的固定点迭代每轮循环遍历所有主机和漏洞只要存在一条满足前提条件的攻击边就加入攻击图并把后置条件并入状态集合直到没有任何新状态产生。这个循环必然收敛因为状态集合是有限的。参数上注意 initial_state 需要包含攻击者最初的立足点比如“外网可达”或”已控制主机 A”否则推导出来的攻击图可能为空。vulnerability_db 的查询接口如果按主机名过滤那么在 topology 里主机命名必须和漏洞库里的命名规则一致。3.3 为什么选 DOT 格式而不直接输出 PNG 或 PDFDOT 是 Graphviz 的图形描述语言纯文本、易解析、可版本控制。从工程角度选 DOT 是聪明的PNG 是二进制没法 diffPDF 不方便二次处理而 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 用 aptmacOS 用 brewWindows 用 choco sudo apt-get install -y graphviz graphviz-dev逻辑说明前两行创建并激活虚拟环境隔离依赖避免污染系统 Pythonpip 安装四个 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.dot5.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_outputTrue, textTrue) 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, encodingutf-8) as f: json.dump(output_summary, f, ensure_asciiFalse, indent2)这段脚本把攻击图生成和批量管理串起来了遍历 topologies 目录下的每个 .dot 拓扑文件调用 generator.py 生成对应的攻击图和 PDF再把结果汇总成一个 JSON 摘要。注意 subprocess.run 里的 capture_output 一定要保留这样生成失败时能拿到 stderr 的输出做排查否则几十个分段里有一个跑挂了你都不知道挂在哪。count_nodes 和 count_edges 是两个需要自己实现的辅助函数思路和前面 grep 命令一样——在生成的 DOT 里数节点标识和 - 边的数量。批量跑完再谈产出质量验证。攻击图的正确性不能靠肉眼扫尤其是分段多的时候。我一般会挑几个关键路径做断言指定一个源主机和一个目标主机在生成的攻击图上跑最短路径算法验证是否存在一条可达路径。如果拓扑和漏洞库明明包含该路径但生成的攻击图里找不到说明生成逻辑有遗漏或者漏洞库里的前提条件和拓扑数据不自洽。最后说一个这半年养成的习惯很多项目的自动生成逻辑看着像黑匣子跑完拿到结果就算完事。但攻击图这种安全产物生成完一定要做一次交叉验证。我的做法是随机抽取 5% 的攻击边手工检查对应漏洞的 CVSS 分数和利用条件是否匹配。这样做的目的不是怀疑代码而是确认输入数据的质量没有问题——漏洞库里的历史遗留条目、运维改了端口没更新拓扑这些事都会让生成结果变得不可信。从那以后我每次生成攻击图都强制走一遍流程先跑批量脚本再对比上一版差异最后做抽样验证。这套组合拳打下来攻击图作为安全决策依据的可靠性才真正立得住。希望帮到你。本文还有配套的精品资源点击获取