简介:面向CTF线下AWD(Attack-Defence)赛事的脚本合集,专为想在攻防对抗中快速得分、又不愿从零编写工具的选手准备,覆盖AWD及AWD Plus常见比赛场景,对初学者和经验型玩家均有实用价值。压缩包共34个文件,整体仅3.18MB,内容以Python脚本(12个)、PHP文件(6个)、文本笔记(7个)为主,另含pyc预编译模块、一个RAR工具包及单个exe程序,分别承担自动化攻击、漏洞利用、防御加固与日志分析等任务。已有3008人学习下载,热度可观。内部按Prepare-for-AWD-master组织,提供扫描探测、SQL注入与XSS利用、不死马批量生成/查杀、Web日志安全分析、WAF防护脚本等模块,并附有操作笔记与命令提示,可直接在合法CTF环境中部署使用,帮助参赛者缩短临场调试时间、提升攻防效率。
1. CTF线下AWD脚本合集:先把生存率拉满,再谈骚操作
参加过线下CTF的人都有这种体验:AWD(Attack With Defense)赛制下,60秒一轮的flag刷新周期,全场靶机都在被别人打,自己的靶机也可能已经被种了不死马。这时候最缺的不是某个0day,而是一套能立刻部署、能止血、能抢分的脚本合集。CTF线下AWD脚本合集就是干这个的——它把批量提交flag、查杀不死马、快速均码、流量分析、WAF防护这些高频动作打包成一个个可以直接跑的脚本,帮你把精力从重复劳动里解放出来,放到真正需要人脑判断的攻击路径上。这个合集适合三类人:第一次打线下赛的新手战队、想把手动操作自动化的大二大三学生、以及需要在赛前快速搭一套防御基座的安全从业者。别指望脚本合集能替你赢比赛,但它能保证你在比赛前30分钟不崩盘。
2. 先看懂AWD的攻防节奏,再决定脚本怎么选
2.1 60秒一轮的抢分循环:每个脚本挂在哪一环
AWD的计分规则直接决定了脚本的用途。主办方会在你的靶机上运行一个flag生成程序,每隔60秒左右生成一个新的flag字符串,你需要把这个字符串submit到裁判系统,submit成功才能得分。与此同时,其他队伍的靶机也在执行同样的流程,你的攻击脚本如果能从他们的靶机里把flag读出来并提交,等于替他们得分,这分就算你的。
这个循环里,最机械的动作就是"读flag、提交flag"。手动完成一次大约需要3到5秒,而一轮只有60秒,攻击脚本跑一次可能拿到二三十个队的flag,手动根本来不及submit。所以批量提交器是整个合集的刚需,比赛刚开始的头10分钟,它就是你的得分主力。
第二个关键动作是防守。你的靶机一上线就会被扫描,Web目录下可能被种PHP一句话木马,甚至是不死马——删掉文件后它会通过memcache、数据库会话或者其他方式再生。你需要在被种马之后最快速度发现并清掉,否则比分会被不断扣掉。查杀不死马的脚本必须常驻运行,至少要做到定时扫描。
2.2 按任务分类脚本:批量提交、不死马查杀、流量回放、Flag嗅探
我一般把脚本合集按用途分成四类:
第一类是批量提交脚本,核心功能是自动从多个靶机上读取flag、批量POST到裁判系统,处理提交频率限制和去重逻辑。这类脚本最成熟也最容易写,赛前甚至可以拿上一届的题目练手。
第二类是不死马查杀与防护脚本,功能包括扫描Web目录下最近被修改的文件、识别常见的木马特征函数(eval、assert、system等)、删除可疑文件、对关键目录做只读保护。
第三类是漏洞利用脚本,这需要根据现场靶机的情况来调整,但基础姿势是通用的:扫描开放端口、识别中间件版本、尝试已知CVE和常见弱口令、通过SQL注入或命令执行读取flag。这里要注意,利用脚本写得太激进容易把对面靶机打崩,裁判会认为你是恶意破坏,直接取消资格。
第四类是流量分析脚本,用tcpdump抓网卡流量,用Python解析pcap文件,提取HTTP请求中出现的flag字符串或攻击payload。这部分脚本在AWD里特别实用,因为对方攻击你的流量里往往带着他的利用代码,直接抄回来打他靶机,等于用别人的矛戳别人的盾。
2.3 语言选型:Python为主、Bash为辅助、单文件优先
做AWD脚本合集,不需要微服务,不需要框架,甚至不需要面向对象。我踩过坑:有一年我写了一个带配置文件、日志模块、异常捕获框架的"工程化"工具包,比赛当天发现靶机环境里连pip的依赖都装不全,Python版本还是2.7。从此之后我的原则是:
- 脚本一律单文件,不引入第三方依赖,只用标准库。requests能用urllib替代就绝不用requests,因为目标靶机上很可能没有这个包。
- 能用Bash写清楚的就用Bash,比如定时查shell、统计文件变更,Bash几行就够。Python只在需要正则提取、HTTP交互、pcap解析时出现。
- 代码里不写绝对路径,全部用相对路径或环境变量,因为你不知道靶机上的Web目录是/var/www/html还是/usr/share/nginx/html。脚本头部统一用
os.getenv或参数传入来指定。
这种"环境友好型"脚本虽然在本地看着不像正规军,但在AWD那种不给你装依赖的时间点上,能跑就是硬道理。很多脚本合集之所以翻车,不是因为逻辑不对,而是因为环境依赖太重,上了靶机连运行都运行不起来。
3. 核心脚本逐个拆解:从提交器到不死马查杀
3.1 批量Flag提交器:别把时间耗在网页上
比赛开始后,你唯一想自动化的就是"提交flag"。我见过有人用浏览器手动刷,一轮刷不过来就得不到了。下面这个脚本是常见的做法,用urllib替代requests,直接用Python标准库完成批量提交:
#!/usr/bin/env python3 """AWD批量flag提交器 - 单文件版""" import urllib.request import urllib.parse import time import re import os # 裁判系统提交接口,按实际赛制修改 SUBMIT_URL = "http://10.0.0.1/api/submit_flag" # 从环境变量读取token,避免硬编码 TOKEN = os.getenv("SUBMIT_TOKEN", "game_token") def submit_flag(flag: str) -> bool: """提交单个flag,返回是否提交成功""" data = urllib.parse.urlencode({ "flag": flag, "token": TOKEN }).encode("utf-8") req = urllib.request.Request(SUBMIT_URL, data=data) try: with urllib.request.urlopen(req, timeout=5) as resp: body = resp.read().decode("utf-8", errors="ignore") # 裁判系统一般返回success或fail字符串 if "success" in body.lower(): return True return False except Exception as e: print(f"[!] 提交失败: {flag} - {e}") return False def submit_batch(flag_file: str, max_retry: int = 3): """读取文件中的flag列表,按行提交并去重""" seen = set() with open(flag_file, "r", encoding="utf-8", errors="ignore") as f: for line in f: flag = line.strip() if not flag or flag in seen: continue seen.add(flag) ok = False for attempt in range(max_retry): if submit_flag(flag): ok = True break time.sleep(1) # 重试间隔,避免被限流 print(f"[{'成功' if ok else '失败'}] {flag}") if __name__ == "__main__": # 用法: python submit.py flags.txt import sys if len(sys.argv) != 2: print("Usage: python submit.py <flag_file>") sys.exit(1) submit_batch(sys.argv[1])这段代码的逻辑很简单但有几个讲究。seen集合做去重,因为一个flag被提交成功后再次提交是无效的,还会拖慢速度;max_retry控制重试次数,因为AWD现场网络抖动很常见,一次超时就放弃会白白丢分;TOKEN从环境变量读取而不是硬编码到脚本里,这样队友拷贝脚本时不会把token带走。
参数调整上,timeout=5和time.sleep(1)是两个关键值。如果裁判系统响应快,可以把timeout压到3秒,重试间隔压到0.5秒,提升吞吐量;如果现场频繁限流,要把时间放宽到2秒以上。不做设置的后果就是提交器被裁判系统判定为攻击行为,IP被拉黑,全队集体丢分。这个真不是玄学,我见过连续三年都有人栽在这里。
3.2 不死马查杀脚本:连接、特征、清杀三步走
不死马(常驻WebShell)是AWD里最让人头疼的东西。它通常用异步连接或写入系统计划任务的方式让自己从内存中恢复,单纯删掉文件没有任何意义。查杀脚本我一般做成三步走:第一步连接靶机,第二步扫描Web目录和系统计划任务,第三步删除可疑文件并关闭持久化通道。
#!/bin/bash # AWD不死马查杀脚本 - 在靶机上用root权限执行 # 用法: ./kill_webshell.sh /var/www/html WEB_DIR="${1:-/var/www/html}" echo "[*] 步骤一: 扫描最近5分钟内被修改的PHP文件" find "$WEB_DIR" -name "*.php" -mmin -5 -type f 2>/dev/null | while read f; do echo "[+] 最近被修改: $f" # 提取可疑特征函数 grep -lE "eval\(|assert\(|system\(|exec\(|passthru\(" "$f" 2>/dev/null done echo "[*] 步骤二: 检查系统计划任务中是否有恶意任务" crontab -l 2>/dev/null | grep -iE "wget|curl|php|python" || echo "[/] 计划任务干净" echo "[*] 步骤三: 删除特征明显的WebShell文件" find "$WEB_DIR" -name "*.php" -type f 2>/dev/null | while read f; do if grep -qE "preg_replace\s*\(\s*['\"/e" "$f" 2>/dev/null; then rm -f "$f" echo "[-] 已删除: $f" fi done echo "[*] 查杀完成。建议立即重启php-fpm或Apache清理内存马。"这个脚本要解释几个关键点。-mmin -5表示只看5分钟内被修改的文件,因为不死马一旦进场,一定会在短时间内留下痕迹;crontab检查是为了防止对手把重启命令写进计划任务里,你删了文件它又拉回来;preg_replace的/e修饰符是早年PHP木马的经典特征,现在虽然host不支持了,但老靶机仍然可能中招。
更重要的是"清杀"之后的操作。删掉文件不等于安全,因为你不知道对方是不是把马种在了缓存目录、session目录或者其他可写目录里。查杀脚本之后,正确操作是:
chmod -R o-w /var/www/html # 取消Web目录的其他用户写权限 chattr +i /var/www/html/index.php # 锁定关键文件,防止被覆盖这两条命令才是防守的核心。关闭文件写权限后,对方即使有写入漏洞也写不进新马;chattr +i锁死文件后,连root都不能随意修改。当然,这也意味着你自己也不能在线更新代码,所以要在确认自己的代码没问题之后才执行。
3.3 开局均码脚本:把每台靶机调成同一配置
均码(统一配置)听起来像运维操作,但在AWD里有另外一个作用:减少被攻击面。如果你的靶机和别人的靶机不一样,比如多开了一个管理后台,多留了一个测试接口,那就是天然的突破口。均码脚本的意义在于,把靶机环境恢复到主办方给出的初始配置,关闭不必要的端口和文件。
#!/bin/bash # AWD开局均码脚本 - 根据主办方提供的环境说明做收敛 # 用法: ./hardening.sh # 1. 备份原有配置 BACKUP="/tmp/awd_backup_$(date +%s)" mkdir -p "$BACKUP" cp -r /var/www/html "$BACKUP/html_default" 2>/dev/null cp /etc/nginx/nginx.conf "$BACKUP/nginx.conf.bak" 2>/dev/null cp /etc/apache2/apache2.conf "$BACKUP/apache2.conf.bak" 2>/dev/null echo "[*] 备份完成,存于 $BACKUP" # 2. 删除常见危险文件 remove_if_exists() { [ -f "$1" ] && rm -f "$1" && echo "[-] 删除 $1" } remove_if_exists /var/www/html/phpinfo.php remove_if_exists /var/www/html/test.php remove_if_exists /var/www/html/info.php remove_if_exists /var/www/html/backup.zip remove_if_exists /var/www/html/.git/config # 3. 修改默认口令 if [ -f /var/www/html/config.php ]; then sed -i "s/'password'\s*=>\s*'[^']*'/'password' => 'Awd_'$(openssl rand -hex 4)/g" /var/www/html/config.php echo "[*] 数据库口令已随机化" fi # 4. 只保留80和443端口 iptables -A INPUT -p tcp --dport 3306 -j DROP iptables -A INPUT -p tcp --dport 22 -j DROP iptables -A INPUT -p tcp --dport 8080 -j DROP echo "[*] 均码完成。非必要端口已关闭。"这里最容易被忽略的是数据库端口。很多AWD赛制里,选手是可以直连数据库读取flag的,这意味着3306端口一旦暴露,对方就能直接连你的MySQL把数据拖走。iptables规则里把3306直接DROP,等于切断了对方从数据库端口入场的路径。如果你自己还要用数据库,那就限定白名单IP访问:
iptables -A INPUT -p tcp --dport 3306 -s 你所在子网 -j ACCEPT iptables -A INPUT -p tcp --dport 3306 -j DROP均码脚本的坑在备份策略上。cp -r如果目标目录已有同名文件,默认覆盖可能会把你自己修改过的代码也覆盖掉。我建议先把整个Web目录打成一个tar包,而不是简单复制,这样出问题时可以直接解包恢复。比赛现场没人会花时间逐文件对比差异,有备份包才算有后悔药。
3.4 漏洞利用脚本的边界:能用但别迷信
AWD的漏洞利用脚本是最具神话色彩的部分。外行以为这是万能钥匙,内行知道这只是一张入场券。常见的利用脚本包括:扫描同一网段内所有存活靶机的Web服务、测试常见SQL注入点、尝试命令执行端点和文件上传漏洞。这些脚本可以写,但你必须清楚它的边界。
#!/usr/bin/env python3 """AWD网段扫描 + 常见Web漏洞探测 - 单文件版""" import socket import ipaddress import urllib.request import urllib.error import sys import re def check_port(host: str, port: int, timeout: float = 1.0) -> bool: """检测目标主机端口是否开放""" try: s = socket.create_connection((host, port), timeout=timeout) s.close() return True except OSError: return False def try_flag_read(host: str) -> str: """尝试从常见flag路径读取flag""" paths = [ "/flag", "/flag.txt", "/flag.php", "/files/flag", "/var/www/html/flag" ] for path in paths: url = f"http://{host}{path}" try: req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) with urllib.request.urlopen(req, timeout=2) as resp: body = resp.read().decode("utf-8", errors="ignore") if re.search(r"[a-zA-Z0-9]{20,}", body): return f"{url} -> {body.strip()}" except Exception: pass return "" def main(network: str): """扫描网段内所有主机的80端口并尝试读取flag""" for ip in ipaddress.ip_network(network, strict=False).hosts(): ip_str = str(ip) if check_port(ip_str, 80, timeout=0.5): result = try_flag_read(ip_str) if result: print(f"[+] {result}") else: print(f"[*] {ip_str}:80 开放,但未直接读取到flag") if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python scan_web.py 192.168.1.0/24") sys.exit(1) main(sys.argv[1])注意这个脚本的两个阈值:timeout=0.5是端口探测的超时,对AWD这种高强度并发场景来说,0.5秒已经是比较激进的设置,再短就容易漏报;timeout=2是HTTP请求的超时,因为对方靶机可能负载很高,响应慢是常态。如果你把这两个值调成1秒,整个扫描可能漏掉一半存活主机。
但这脚本最大的问题不是性能,而是它太"理想化"。现实比赛里对面靶机可能压根不在80端口提供Web服务,可能在8080甚至随机端口;常见的flag路径也可能被主办方改了名字。所以利用脚本之前,最好先跑一次pcap流量分析,看看对手是怎么攻击你的——他用了哪个端口、哪个路径,你照着改脚本去打他,成功率高得多。
4. 流量分析与WAF部署:防守侧的自动应答
4.1 用tcpdump抓流量,再用Python解析攻击迹
在AWD现场,你的靶机会收到大量攻击流量,这些流量里不仅藏着对方的利用方式,有时候对方提交flag的HTTP请求也会从你网卡上经过。抓流量是防守的第一步,也是拿到攻击者思路的最快途径。
#!/bin/bash # AWD流量抓取脚本 - 后台运行,抓取网卡流量并滚动保存 # 用法: ./sniffer.sh eth0 ./pcap IFACE="${1:-eth0}" OUTDIR="${2:-./pcap}" mkdir -p "$OUTDIR" # 按10分钟滚动保存,每个文件不超过200MB exec tcpdump -i "$IFACE" -nn -s 0 \ -w "$OUTDIR/awd_$(date +%Y%m%d_%H%M%S).pcap" \ -G 600 -W 50 \ "tcp port 80 or tcp port 443 or udp port 53"-G 600表示每600秒生成一个新文件,-W 50表示最多保留50个文件,超过就删除最旧的。这样抓一整天流量最多占5GB磁盘,不会把靶机磁盘打满。-s 0表示抓取完整数据包,不要裁剪,因为flag或payload可能藏在包尾。
抓到流量之后,用Python解析pcap提取关键信息:
#!/usr/bin/env python3 """从pcap文件中提取HTTP请求中的flag特征和攻击payload""" import os import re import sys # 使用dpkt解析pcap,如果没有dpkt则跳过HTTP解析 try: import dpkt except ImportError: print("[-] 需要安装dpkt: pip install dpkt") sys.exit(1) def extract_http_flags(pcap_file: str): """从HTTP流中提取疑似flag的字符串""" flag_pattern = re.compile(r"flag\{[^}]+\}", re.I) attack_patterns = [ r"eval\(", r"base64_decode\(", r"system\(.*whomai", r"cat\s+/flag", r"/tmp/", r"\.php\?cmd=", ] with open(pcap_file, "rb") as f: pcap = dpkt.pcap.Reader(f) for ts, buf in pcap: try: eth = dpkt.ethernet.Ethernet(buf) if eth.type != dpkt.ethernet.ETH_TYPE_IP: continue ip = eth.data if ip.p != dpkt.ip.IP_PROTO_TCP: continue tcp = ip.data # 过滤出HTTP流量 if len(tcp.data) == 0: continue payload = tcp.data.decode("utf-8", errors="ignore")[:2048] if "HTTP" in payload or "GET" in payload or "POST" in payload: # 提取flag for m in flag_pattern.findall(payload): print(f"[FLAG] {m} from {ip.src}") # 提取攻击payload for pat in attack_patterns: if re.search(pat, payload, re.I): print(f"[ATTACK] {pat} from {ip.src}") except Exception: continue if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python parse_pcap.py <pcap_file>") sys.exit(1) extract_http_flags(sys.argv[1])这段代码本质上就是一个极简的IDS。它不做深度解码,只提取明文HTTP流量中的特征字符串,但AWD里绝大多数Web攻击都是明文HTTP,足够用了。attack_patterns列表里写的都是真实出现过的特征,比如base64_decode(代表对方尝试用编码绕过,cat /flag代表对方已经能执行命令。每抓到一条,你就多一条针对对手的利用思路。很多人以为AWD的攻击手在脑内进行复杂推演,实际上高手都在抄流量包。
4.2 WAF规则:拦截常见PHP攻击特征但不误伤队友
AWD的WAF部署和线上业务WAF不同,不需要高并发,不需要语义分析,只需要规则精准且能快速更新。最常见的做法是改Nginx层拦截。
# 在nginx.conf的server段中加入防注入和防文件包含规则 # 重点拦截: SQL注入、XSS简单变体、命令执行、PHP文件包含 location ~* \.(php|asp|jsp)$ { # SQL注入基础拦截 if ($query_string ~* "union.*select.*from") { return 403; } if ($query_string ~* "\.\./\.\.") { return 403; } if ($query_string ~* "base64_decode.*") # PHP文件包含拦截 if ($query_string ~* "include.*php") { return 403; } if ($query_string ~* "data://") { return 403; } if ($query_string ~* "php://filter") { return 403; } # 命令执行拦截 if ($query_string ~* "system.*cat") { return 403; } if ($query_string ~* "passthru") { return 403; } # 上传目录禁止执行PHP location ^~ /upload/ { location ~* \.php$ { return 403; } } }这个WAF配置的精髓在最后一段:/upload/目录下的所有PHP请求直接403。攻击者的惯用手法是先传一个PHP幻术图片,再通过文件包含漏洞去执行它。把上传目录的执行权关掉,这整条攻击链就断了一半。这比你在应用层写一堆if判断要高效得多,而且不需要修改业务代码。
WAF规则最大的坑是"误伤队友"。AWD是允许选手之间互相攻击的,你的WAF如果拦截了正常提交flag的操作,那比被打还要致命。我在比赛里干过这事——把flag字符串当作黑名单关键词写进WAF,结果自己队的submit请求被自己的Nginx拦了,整整10分钟没提交上分。从那之后我给自己定了一条规矩:WAF规则里绝对不碰"flag"这个纯字符串,只拦攻击特征,不拦数据内容。
4.3 日志监控告警脚本:用最少资源发现被攻破
流量抓取是被动的,WAF拦截是主动的,但真正的风险在于对方已经打进你的靶机但你还没察觉。一个轻量的日志监控脚本能解决这个问题,不用引入ELK,不用安装Agent,几行Bash就能做到。
#!/bin/bash # 日志监控告警 - 每隔30秒扫描一次Web日志,发现可疑请求立即告警 # 用法: ./log_monitor.sh /var/log/nginx/access.log LOG_FILE="${1:-/var/log/nginx/access.log}" CURSOR_FILE="/tmp/awd_log_cursor" INTERVAL="${2:-30}" # 记录上次扫描到的行数 if [ ! -f "$CURSOR_FILE" ]; then wc -l < "$LOG_FILE" > "$CURSOR_FILE" fi while true; do OLD_POS=$(cat "$CURSOR_FILE") NEW_POS=$(wc -l < "$LOG_FILE") if [ "$NEW_POS" -gt "$OLD_POS" ]; then # 提取新增日志中的可疑请求 tail -n +$((OLD_POS + 1)) "$LOG_FILE" | \ grep -iE "\.\./|union.*select|php://|system\(|eval\(|/tmp/|\.bak|\.sql" | \ while read line; do NOW=$(date +%H:%M:%S) echo "[$NOW] [WARN] 可疑请求: $(echo $line | cut -c1-200)" # 可选: 通过curl发送到团队告警群 done fi echo "$NEW_POS" > "$CURSOR_FILE" sleep "$INTERVAL" done这个脚本的资源占用几乎可以忽略不计,但效果很直接。每当有攻击者尝试路径穿越、SQL注入、文件包含时,日志里会留下痕迹,grep模式会在30秒内发现并输出告警。这里最值得调的参数是INTERVAL——我在实际使用时设成15秒,因为攻击者在AWD里的操作窗口很短,30秒他可能已经种完马走人了。但如果你靶机配置低,15秒的轮询也会造成一定负载,需要做一个取舍。
告警之后,别急着去删文件。先顺着IP查对方的行为链条,看他尝试了哪些路径、用了哪些参数。很多时候你看到的不只是攻击尝试,而是对方完整的利用流程,这比你自己Fuzz半天的收获大多了。
5. AWD脚本合集的翻车现场:我踩过的坑都在这了
5.1 Flag提交器把自身作业给提交了
现象:批量提交器跑了几分钟后,提交成功率突然变为0,日志里全是重复提交失败。 原因:脚本设置了while True无限重试,没做flag去重,立刻把已经提交过的flag又提交了一遍。更糟的是,脚本从靶机A读到了靶机B的flag,B队已经把同样的flag提交过了,你这属于替别人重复得分,裁判系统会拒绝。 解决:把脚本改成"只提交未出现在本地历史记录中的flag",用seen集合持久化到本地文件,每次运行先加载上次的结果。同时,对同一个flag设置冷却时间,至少5分钟之内不重复提交。这是脚本合集里最不该犯的错,但每年都有人犯。
5.2 查杀不死马时把自家正常后门也删了
现象:对自己的靶机跑查杀脚本后,靶机上的正常管理功能全部失效,比赛后半程想更新代码都没法操作。 原因:查杀脚本只按特征匹配,grep -E "eval|system|exec"把业务代码中正常调用的exec函数也识别为木马,直接删除。 解决:查杀脚本里加一个白名单机制,对已知的正常文件做哈希记录,只删除"不在白名单且命中多个特征"的文件。判定条件从"一个特征命中"升级为"至少命中3个特征且文件不在白名单"。我在脚本里专门写了一个KNOWN_GOOD_SHA256列表,赛前把主办方的初始代码全部跑一遍,记录哈希,比赛过程中只扫哈希变化过的文件。
5.3 iptables规则把自己SSH断掉
现象:均码脚本执行后,自己无法SSH登录靶机,远程操作完全瘫痪。 原因:均码脚本里执行了iptables -A INPUT -p tcp --dport 22 -j DROP,本意是关掉SSH防止别人进,但自己也没留白名单。 解决:在执行任何DROP规则之前,先确认自己的IP在允许列表里:
MY_IP=$(curl -s ifconfig.me 2>/dev/null || echo "未知IP") iptables -A INPUT -p tcp --dport 22 -s "$MY_IP" -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j DROP这个顺序不能反过来,先加白再DROP。另外,建议在执行脚本前先开一个临时screenshot会话,就算iptables崩了,还能通过物理终端恢复。这个坑我翻过一次车,比赛到一半只能找裁判要控制台权限,特别狼狈。
5.4 WAF拦截了自己队的批量submit请求
现象:攻击流量确实被拦截了,但自己的得分也开始直线下降。 原因:Nginx的if ($query_string ~* "flag")把submit请求的body当成query string检查,凡是带"flag"字符串的请求全部403。 解决:如前面说的,不要把"flag"当黑名单关键词。WAF拦截对象应该是攻击特征,而不是数据内容——flag本身是正常业务数据。我最后写的WAF规则只匹配union select、../../、php://filter、system(这类攻击语法,完全不管请求里带不带"flag"。
5.5 流量分析脚本被现场海量垃圾流量淹没
现象:抓了10分钟流量,pcap文件已经几个GB,解析脚本跑了一个小时还在处理前10分钟的包。 原因:AWD现场同网段有几十台靶机,广播包、ARP包、大量扫描流量全都被抓了进来,有效HTTP请求只占很小比例。 解决:抓包时就用过滤条件收缩范围:只抓TCP端口80和443的数据,且只抓发往自己靶机IP的流量。如果你不知道攻击者的IP范围,可以抓目标为自身的流量:
tcpdump -i eth0 -nn -s 0 -w output.pcap "tcp port 80 and dst host 你的靶机IP"dst host直接限定目标,能过滤掉80%的无关流量。这个改动看着小,但直接影响你能不能及时解析出攻击payload。
5.6 脚本合集的版本混乱,上靶机才发现是旧版
现象:比赛当天从网盘/优盘拷脚本到靶机,运行报错,参数对不上,队友之间互相传的副本还不一致。 原因:脚本合集没有做版本管理,迭代了20多个版本,文件名还都一样。有人改了代码没改文件名,传到群里又被人覆盖。 解决:每份脚本头部都要写清楚VERSION和CHANGELOG,文件名也要带版本号,例如submit_flags_v2.1.py。最好赛前把所有脚本打包成一个tar.gz,用固定的压缩包名字存到U盘和网盘两个地方,上靶机解压时先比对md5。这一点看起来是管理问题,但每年都有队伍因为脚本版本不一致临时改代码浪费30分钟,这30分钟够别人拿三次flag了。
6. 让脚本合集真正跑起来:从本地验收到赛前预演
6.1 赛前48小时的脚本演练清单
脚本写出来不是终点,能在比赛现场稳定运行才是。我建议赛前48小时做一次完整演练,严格模拟比赛环境:
| 检查项 | 操作 | 通过标准 |
|---|---|---|
| 提交器端到端 | 用一个临时账号向裁判系统提交100次随机flag | 提交成功且无超时 |
| 查杀脚本有效性 | 手动在Web目录放一个测试马,跑脚本删除 | 测试马被清,正常代码未被误删 |
| WAF规则回归 | 模拟SQL注入、路径穿越、文件包含三类攻击 | 全部返回403,正常业务不受影响 |
| 流量抓取 | 后台跑10分钟tcpdump,检查文件大小和解析结果 | pcap文件可正常解析出HTTP请求 |
| 均码脚本收敛 | 在当前靶机跑完,检查所有非必要端口已关闭 | 只剩80/443和SSH白名单 |
| 脚本合集md5 | 打包并记录所有脚本的md5 | U盘和网盘文件md5一致 |
这张表里的任何一项在实战中翻车,都足以让你整场比赛陷入被动。演练不是走流程,我发现可以每次都发现至少一个问题——有年演练时发现提交器在Windows靶机上Python编码有问题,改了三行才修好;还有年WAF规则漏了data://协议,补上后才放心。
6.2 应急时只信任自己能解释的脚本
脚本合集再全,也有覆盖不到的意外场景。我的经验是:在应急情况下,只运行完全看得懂逻辑的脚本,绝不执行看不懂的"大神脚本"。这句话背后的教训很深刻——有一年别人传给我一个"万能查杀脚本",说是能清所有马,我信了,跑完之后发现靶机上的Web服务直接起不来了,后来排查发现脚本里有一段rm -rf /tmp/*,把PHP的session文件全删了,直接导致服务不可用。
应急时你需要的是把原理讲清楚的脚本:提交器知道你提交的URL是什么、查杀脚本知道它删了哪个文件、WAF规则知道你拦了哪种攻击。但凡有任何一个环节是黑匣子,都不要让它出现在正式比赛里。CTF线下AWD脚本合集的价值在于"可控的自动化",而不是"神秘的武器库"。
6.3 快速验证脚本、学会给自己留后悔药
最后一个习惯和验证有关。每次修改脚本后,不要只跑一次就收工。我的标准流程是:先在本地起一个最小靶场环境(一个容器或者一台虚拟机跑Nginx+PHP),放上测试flag和测试木马文件,跑一遍全套脚本。这个验证过程每次不超过10分钟,但能拦住80%的翻车场景。
同时,任何操作之前先想好怎么回滚。均码脚本跑出问题要能从备份恢复、WAF拦错流量要能快速关掉规则、ipset拉黑要能清空列表。我给每台靶机的/tmp都放了一个自己写的rollback.sh,内容很简单:调用备份目录里的原始配置覆盖回去,然后重启服务。这个脚本在比赛后半段几乎都会用到——当你发现某个限制条件误伤了正常业务,立刻执行它就能恢复。
AWD比赛不是写代码能力的较量,而是"在极度紧张和噪音环境下保持操作正确性"的较量。脚本合集帮你把高频操作变成半自动,但半自动的前提是你能解释它、信任它、控制它。我这几年的感受是:与其囤积各种华丽的脚本库,不如把自己最常用的五个脚本打磨到极致,并且知道它们的每一行在干什么。希望这份经验对你有用,祝比赛顺利。
本文还有配套的精品资源,点击获取