ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

自动化攻击图生成器:源码拆解与避坑指南

自动化攻击图生成器:源码拆解与避坑指南 简介基于Python的自动化攻击图生成器完整源码面向安全分析师、渗透测试与网络运维人员核心价值在于将复杂网络中的潜在攻击路径自动映射为可视化攻击图帮助快速定位漏洞关联与风险暴露面降低攻击路径梳理的技术门槛。压缩包共101个文件、37.75MB类型覆盖44个JSON/YAML配置文件、10个DOT图形描述文件、10个PDF报告、8个Python源码与7个Pyc字节码、5个ZIP、Shell脚本及Git属性文件等兼顾配置解析、图形渲染、报告输出与自动化部署。当前已有321人学习该资源包适合需要快速上手攻击图建模、安全分析课程设计或企业蓝队评估的中初级学习者。源码工程化程度较高内置可修改的拓扑与漏洞配置读者能直接调整输入重新生成DOT/PDF结果借此理解攻击链推导逻辑配套的Shell脚本与Git配置则大大简化环境准备和版本管理便于二次开发与团队协作。1. 从拓扑到攻击路径为什么需要自动化攻击图生成器安全分析师拿到一份漏洞报告时最头疼的不是漏洞清单本身而是这些漏洞之间怎么串成攻击者实际会走的路径。基于 Python 的 attack-graph-generator 自动化攻击图生成器源码就是为解决这个问题而生的输入网络拓扑与漏洞数据输出带方向、带漏洞利用标签的攻击图 DOT 文件过去需要人工梳理数天的攻击路径分析压缩成一条可重复执行的生成流程。这份源码共 100 个文件混杂着 DOT、JSON、YAML、Python、Shell 和字节码第一眼看上去有些劝退但拆解后你会发现它的模块边界相当清晰。本文从文件构成、依赖选型、运行复现到踩坑记录完整过一遍适合正在做安全评估、又不想从零手写攻击图工具的人。2. 先读文件再动手attack-graph-generator 的目录构成与执行链路2.1 两种 DOT 文件拓扑图与攻击图的本质区别打开压缩包最先引起注意的是topology_graph.dot和attack_graph.dot反复出现。这两个文件名字接近但职责完全不同。topology_graph.dot描述的是网络实际长什么样有哪些主机、哪些网络设备、主机之间怎么连通。attack_graph.dot描述的是攻击者视角从哪个入口进来、经过哪些中间节点、利用什么漏洞、最终到达哪个目标。拓扑图是输入攻击图是输出生成器的核心工作就是把前者转换成后者。DOT 是 Graphviz 的图形描述语言。拓扑图用无向边表示物理或逻辑连通关系攻击图用有向边表示攻击步骤的先后依赖。这是理解整个项目的第一把钥匙看到无向边说明还在拓扑层看到有向边且带上漏洞标签已经进入攻击路径层。一个典型的拓扑图 DOT 片段长得像这样graph topology { web-server [shapebox, color#2c3e50]; db-server [shapebox, color#2c3e50]; 192.168.1.10 [shapeellipse, color#7f8c8d]; user-host [shapeellipse, color#7f8c8d]; user-host -- web-server; web-server -- 192.168.1.10; 192.168.1.10 -- db-server; }graph topology声明这是一个无向图--定义边。这里的椭圆节点代表普通主机方框节点代表服务器边的含义是「网络层可达」。生成器读取这份文件时并不需要自己去画图而是把它当成结构化数据解析——节点列表、边列表、节点类型这些信息决定了后续漏洞匹配的搜索空间。攻击图则完全不同它必须是有向图digraph attack_graph { attacker - web-server [labelexploit: CVE-2021-44228]; web-server - 192.168.1.10 [labelpivot: ssh_bruteforce]; 192.168.1.10 - db-server [labelexploit: CVE-2019-19781]; }digraph声明有向图-定义有向边label标明这一步的攻击动作。注意第三条边的起点不是attacker而是内网的一台中间主机——这正是攻击图的价值它不是漏洞清单而是攻击者可到达性的推理结果。生成器要做的事就是在拓扑图上做可达性分析把「谁能到达谁」逐层展开标注上对应的利用方式最终形成这样一条完整链条。2.2 配置文件的格式分工JSON、YAML 与属性文件各管一摊项目里同时出现 JSON、YAML 和属性文件不是作者随意堆砌而是三种格式各有擅长场景。理解这个分工你改配置时才不会找错文件。JSON 格式适合描述树形结构、字段可选的复杂对象。漏洞字典就非常适合用 JSON每条漏洞的属性集合并不固定有的带 CVSS 评分有的带利用条件有的只有描述文本JSON 天然支持这种非严格的 schema。{ cve-2021-44228: { service: log4j, cvss: 10.0, exploitable: true, target: web-server }, cve-2019-19781: { service: citrix-gateway, cvss: 9.8, exploitable: true, target: 192.168.1.10 } }这段 JSON 里最关键的字段是service和exploitable。service决定漏洞匹配拓扑节点时参照什么服务标识exploitable决定该漏洞是否参与攻击图构建。实际使用中我一般会把exploitable设为false的漏洞当作「仅记录、不生成边」的占位信息这样可以控制攻击图的边数膨胀。YAML 则更适合写主配置。它的缩进结构天然表达层级关系比 JSON 少写大量括号也比属性文件更能表达嵌套。生成器的主参数比如路径深度上限、输出格式、是否渲染 PDF这些放在 YAML 里改起来最顺手。topology: input_file: topology_graph.dot node_types: - web-server - db-server - user-host attack: depth_limit: 5 merge_similar_edges: true ignore_orphan_nodes: true output: format: dot render_pdf: true pdf_path: attack_graph.pdfdepth_limit控制攻击路径的最大深度默认 5 意味着生成器最多向外扩展 5 层防止出现指数级路径爆炸。merge_similar_edges控制相邻节点间相同类型的攻击边是否合并我这边的经验是建议开启否则大量重复边会让渲染结果变成一团黑线。ignore_orphan_nodes决定拓扑图上没有任何漏洞数据关联的孤立节点要不要参与计算开启后能显著减少无效路径。属性文件通常是最简单的那一层存储端口默认值、日志级别、连接超时这类无嵌套结构的键值对。三种格式并存的本质是「结构化程度递增」属性文件管标量YAML 管配置层级JSON 管动态数据。动手改参数之前先判断当前参数属于哪一层能少踩很多解析报错的坑。2.3 源码、字节码与 Shell 脚本一次生成过程的完整链路100 个文件听起来吓人但按职责切分后主体逻辑并不复杂。项目同时包含.py源码和.pyc字节码文件说明打包前执行过 Python 的字节码编译流程。.pyc存在的意义是让首次启动更快——Python 导入模块时会优先加载字节码缓存省去重新编译源码的时间。需要留意的是.pyc是配套编译产物不是独立可运行程序删掉或单独拷走都会导致运行时找不回对应模块。Shell 脚本的存在则说明生成器在自动化部署和执行编排上下了功夫。常见的做法是一个install_env.sh负责装依赖、一个run_pipeline.sh串起「解析配置 → 加载拓扑 → 漏洞匹配 → 路径推理 → 渲染输出」的完整流程。这部分脚本通常比较薄但它是整条自动化链路里不可缺少的胶水层。整个生成器的执行链路概括下来是五步def build_attack_graph(topology: Digraph, vulnerabilities: dict, config: dict): attack Digraph(attack_graph) visited set() for host in topology.nodes(): if host in visited: continue for cve, info in vulnerabilities.items(): if not info[exploitable]: continue if not service_matches(host, info[service]): continue attack.add_edge(host, info[target], labelcve) visited.add(host) visited.add(info[target]) return attack这段摘录是生成逻辑中最核心的「漏洞匹配 连边」部分。service_matches是匹配判定函数——常见实现是拿主机上开放的服务标识与漏洞字典里的service字段做比对但实际项目中这个函数往往会叠加更多条件端口是否开放、目标主机是否可达、当前路径深度是否超过depth_limit。visited集合用来防止同一主机在多次循环中被重复处理避免攻击图出现冗余边。在完整实现里这个函数前面通常还有一层拓扑预处理解析topology_graph.dot提取节点间的连通关系把无向的拓扑连接展开成有向的可达关系。展开之后才轮到漏洞匹配。所以整个执行链路是「拓扑解析 → 可达性展开 → 漏洞匹配 → 路径剪枝 → 输出攻击图 DOT 渲染 PDF」。任何一个环节断了输出都不会正确——这也是后文避坑章节反复出现「生成了但不对」的原因。3. 环境搭建与复现从依赖安装到跑出一张真实攻击图3.1 准备运行环境Python、Graphviz 与解析库复现这个项目前先把依赖环境捋清楚。生成器本身是纯 Python 逻辑但 DOT 文件解析和 PDF 渲染都依赖 Graphviz 工具链这是最容易踩的环境坑很多人只装了 Python 的graphviz库却漏装了系统级的 Graphviz 本体运行时报错找dot命令找不到。先装 Graphviz 本体。Debian/Ubuntu 系的安装命令是sudo apt update sudo apt install -y graphvizmacOS 用 Homebrewbrew install graphvizWindows 用户需要到 Graphviz 官网下载安装包并且安装时勾选「添加到 PATH」选项。装完后必须验证命令行是否能直接调用dotdot -V如果输出类似dot - graphviz version 2.43.0的版本信息说明这里已经通了。如果提示command not found不要急着往下走先解决 PATH 问题——后面所有渲染相关步骤都依赖这个命令。我一般会先做这一步验证再装 Python 库避免后面排错时分不清是 Python 库的问题还是系统工具的问题。接着安装 Python 依赖pip install pyyaml graphviz networkx三个库的作用分别是pyyaml负责解析 YAML 主配置graphviz是 Python 侧的 DOT 封装接口networkx负责图结构分析。需要注意的是pip install graphviz安装的只是 Python 绑定层它本身不包含dot可执行文件运行时仍然去找系统里的 Graphviz——所以刚才那步dot -V验证不能省略。最后确认 Python 版本。项目代码中用到的语法特性需要 Python 3.8 以上建议版本检查方式很简单python --version如果本机默认的python指向的是 2.x或者版本偏低建议用python3显式指定或者直接建一个虚拟环境再操作python3 -m venv venv source venv/bin/activate虚拟环境的好处是隔离依赖避免多个 Python 项目互相污染库版本。这一步在复现开源项目时属于标准操作省得后面出现「在我机器上明明能跑」的灵异事件。3.2 运行生成流程从解压到输出攻击图环境就绪后先把压缩包解压到工作目录。这里有一个细节解压路径不要带空格和中文Graphviz 某些旧版本在路径含非 ASCII 字符时会直接找不到文件后文避坑章节会展开说。unzip attack-graph-generator.zip -d ./attack-graph-generator cd attack-graph-generator进入目录后先别急着跑主程序。考虑到项目里已经带了.pyc字节码文件而这些字节码可能由不同版本的 Python 编译生成稳妥的做法是重新编译一次python -m compileall .这条命令会扫描当前目录下所有.py文件重新生成匹配当前 Python 版本的字节码缓存。它同时起到两个作用一是让后续导入模块时直接用最新字节码避免版本不匹配的加载错误二是能顺带发现语法层面的严重问题——如果某个源码文件语法损坏编译到这里就会报错。接下来运行生成主流程。项目入口脚本的文件名需要看一下目录下的实际命名常见的是main.py或run.py执行方式形如python main.py --config config.yaml --topology topology_graph.dot--config指定主配置文件路径--topology指定拓扑图输入文件。执行过程中生成器会依次解析配置、加载拓扑、匹配漏洞、推理路径最后在输出目录生成attack_graph.dot。如果你在项目里看到的是run_pipeline.sh之类的脚本直接执行它也可以效果一样只是脚本内部还会额外帮你做环境检查和目录清理。跑完后先看一眼输出目录正常情况下至少应该多出attack_graph.dot文件。如果还配了render_pdf: true应该同时出现attack_graph.pdf。只生成了 DOT 而没有 PDF先别怀疑代码回头看 YAML 配置里的render_pdf是不是写成了false——这是最常见的「少输出」原因。3.3 用结果验证生成是否正确生成完毕不等于生成正确。攻击图最容易出现的假阳性是「边数异常少」——漏洞字典里明明配置了几十条漏洞攻击图却只有零星几条边这通常意味着漏洞匹配阶段全军覆没。先做一次快速统计grep -c - attack_graph.dot这个命令统计攻击图中攻击边的总条数。如果你配了 20 条可利用漏洞、拓扑里也有对应服务的主机输出却只有个位数甚至 0那基本可以断定是service_matches逻辑没配上。此时回到漏洞字典 JSON 里检查service字段的命名再对照拓扑节点上开放的服务标识写法两者的格式必须完全一致——比如一边写log4j、另一边写Log4j都会匹配失败大小写和分隔符严格敏感。再做一步渲染验证dot -Tpdf attack_graph.dot -o attack_graph.pdf手动执行这条渲染命令能验证两件事一是attack_graph.dot语法是否合法Graphviz 对不合法的 DOT 文件会直接报错二是确认 PDF 产出环境没有问题。这一步也充当后续自动化流程的预演——如果你打算把生成器接进定时任务这条渲染命令必须以某种形式重复执行要么交给 Python 脚本内部调用要么在 Shell 脚本里保留这一行。验证完应该再看一眼攻击图的结构是否合理。直接用文本编辑器打开 DOT 文件检查边上的label是否与漏洞字典对应。如果攻击路径跳过了中间主机直接连到目标说明可达性分析可能没有生效边被错误合并了——此时回到 YAML 配置把merge_similar_edges临时改成false再跑一遍对比差异。4. 常见问题与避坑生成攻击图时的五个翻车点4.1 渲染出来的 PDF 中文标签变成方框乱码现象DOT 文件里节点和边的 label 包含中文用dot -Tpdf渲染后 PDF 里全是空心方框。原因Graphviz 默认字体不含中文字形它把中文字符映射到 fallback 字体时找不到对应字形于是渲染成方框。这不是攻击图生成器的代码问题而是 Graphviz 渲染链路的经典毛病。解决在 DOT 文件的图属性里显式指定中文字体。常见做法是在文件头加一行graph [fontnameMicrosoft YaHei];Linux 环境换成WenQuanYi Micro Hei或Noto Sans CJK SC。如果你用的是 Python 的graphviz库可以在创建 Digraph 对象时传参Digraph(commentattack, graph_attr{fontname: Noto Sans CJK SC})。另一个更省事的方案是设计标签时统一用英文短标识中文字段只放在 JSON 配置里做内部映射。4.2 攻击图边数爆炸渲染出来是一团黑线现象漏洞字典配了上百条漏洞攻击图生成后 DOT 文件有几十 MBPDF 打开后所有边纠缠在一起根本无法阅读。原因生成逻辑没有做路径剪枝。拓扑图上任意两台主机只要存在连通关系漏洞字典里又有对应漏洞就生成一条边。当主机数量和漏洞数量同时增大边的数量呈乘积式增长最终远超人类阅读极限。这种「生成正确但不可用」的结果比完全空白更隐蔽。解决把 YAML 里的depth_limit从默认值调下来。5 是一个比较均衡的深度上限超过这个深度攻击路径的可利用性已经很低强行保留只会制造噪声。同时把merge_similar_edges设为true让相邻节点间相同类型的攻击边合并成一条并在 DOT 里的 edge 上加penwidth属性区分不同攻击类型的边粗。如果边数仍然爆炸回到漏洞字典把exploitable为false的漏洞统一剔除只保留真正参与路径推导的条目——这是从源头控制边数最有效的手段。4.3 YAML 配置解析报缩进错误现象运行生成器时抛出yaml.scanner.ScannerError或yaml.parser.ParserError提示mapping values are not allowed here。原因YAML 对缩进极其敏感项目中的 YAML 配置文件必须统一用空格缩进。编辑器如果开启了「用 tab 缩进」或者从文档复制配置时混入了不可见字符解析层会直接罢工。这是新手最容易踩的坑而且报错信息往往指向配置文件中间某个看起来很正常的行。解决打开编辑器把缩进设置改成「空格 × 2」或「空格 × 4」禁止 tab。Linux 下可以用cat -A config.yaml检查不可见字符tab 会显示为^I空格不会。修改后重新运行缩进错误基本清零。另外一个隐藏做法删除文件里的注释后重新保存某些配置文件的注释行隐藏着编码异常的空格会在解析时干扰缩进计算。4.4 字节码文件与当前 Python 版本不匹配导入模块报错现象执行主程序时抛出ValueError: bad marshal data或ImportError: invalid bytecode但源码文件看起来完损。原因压缩包里的.pyc文件由另一个 Python 版本编译产出。Python 字节码格式在不同版本间并不兼容尤其小版本跨越较大时解释器加载.pyc直接失败。很多人会把这个问题误判为源码损坏其实源码根本没参与执行——解释器优先加载了过期字节码。解决先清掉旧字节码缓存再重新编译find . -name __pycache__ -type d -exec rm -rf {} find . -name *.pyc -type f -delete python -m compileall .这两条清理命令删掉所有历史缓存最后一个命令重新生成与当前解释器匹配的字节码。从那以后我每次拿到含.pyc的项目都会把这三行当成固定开场白从不在旧缓存上赌兼容性。4.5 解压到中文路径后 Graphviz 找不到输入文件现象主程序正常执行完毕但调用dot -Tpdf渲染时抛Error: dot: cant open attack_graph.dot而文件明明就在当前目录。原因Graphviz 在部分平台对路径编码的处理有历史包袱中文、空格等特殊字符会让文件定位失效。生成器内部虽然用 Python 正确解析了路径但把路径传给dot子进程时编码转换可能出错或缺失引号。解决把整个项目解压到纯英文路径例如~/workspace/attack-graph-generator目录名避开中文、空格和特殊符号。如果你的工作目录已经有中文路径用软链接绕过去ln -s /你的/中文/路径/attack-graph-generator ~/agg cd ~/agg5. 从 DOT 到可交付报告结构验证与定时自动化进阶攻击图生成不等于工作交付真正交付给团队的是解读结论。这里给一个可落地的进阶用法用networkx对生成的攻击图做结构验证确认路径的合法性与最长攻击链路长度然后接一个定时任务实现自动化巡检。以最长攻击路径验证为例import networkx as nx from networkx.drawing.nx_pydot import read_dot g nx.DiGraph(read_dot(attack_graph.dot)) if not nx.is_directed_acyclic_graph(g): print(警告攻击图中存在环路路径推理可能出现错误) entry_nodes [n for n, d in g.in_degree() if d 0] sink_nodes [n for n, d in g.out_degree() if d 0] max_path_len 0 for entry in entry_nodes: for sink in sink_nodes: for path in nx.all_simple_paths(g, entry, sink): max_path_len max(max_path_len, len(path)) print(f入口节点数{len(entry_nodes)}目标节点数{len(sink_nodes)}) print(f最长攻击路径长度{max_path_len})这段脚本用read_dot读入攻击图in_degree 0的节点是攻击起点out_degree 0的节点是可达目标。nx.all_simple_paths枚举所有简单路径统计最长链路。如果攻击图是 DAG最长的入口到目标路径就是最关键的攻击链应该优先出现在交付报告的显眼位置。如果图中出现环路优先怀疑merge_similar_edges开得太激进把不同攻击步骤错误合并成了环。验证通过后把生成流程挂进crontab做周期性巡检。攻击面每天都会变化拓扑文件更新后手动跑一次是常态但接上定时任务之后这套工具才算真正自动化0 2 * * * cd /path/to/attack-graph-generator python main.py --config config.yaml --topology topology_graph.dot run.log 21这段定时任务会在每天凌晨两点执行一次完整生成流程日志写入run.log。排错时先看日志再排查攻击图效率最高。如果担心磁盘占用在脚本里加一段保留最近七份 PDF 的清理逻辑即可。我对这套资源的使用习惯是拿到任何新的攻击图项目先跑dot -V确认渲染链路活着再跑compileall消除字节码兼容隐患最后用networkx的 DAG 检查兜底——这三步是五分钟内完成的例行检查能省掉后面一小时甚至一整天的排错时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表