ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CTF AWD防守工具包详解:从WAF部署到文件监控与flag提交

CTF AWD防守工具包详解:从WAF部署到文件监控与flag提交 简介面向CTF AWD攻防演练的比赛项目源码与工具合集可为参赛选手及安全爱好者解决比赛中文件监控、日志分析、流量监控与flag自动提交等常见需求。压缩包共52个文件以PHP防护脚本、Python自动化脚本、Windows图形化工具为主包含Web防护规则、后门检查脚本、自动提交flag的小工具并辅以动态链接库、样式表、文档说明等支持文件整体约11.89MB轻量便携便于赛前部署。内容中既有安全管理系统、Web日志安全分析工具等现成程序也有pyinotify文件监控脚本和AttackRules规则模板选手可在对抗中直接调用通过实时监控及时发现对手写入的后门借助日志分析还原攻击路径再以自动化脚本持续提交flag。同时附带的说明文档与安装配置指引也方便读者学习攻防思路并二次定制自己的工具链。目前已有507人学习下载适合希望系统梳理AWD攻防工具链的人群。1. CTF AWD 不是拼手速这份工具包的防守逻辑与下载理由CTF AWDAttack With Defense这类攻防兼备的夺旗赛打的是“既要进攻拿分又要保命不被掏空”。很多人第一次上手时把时间全花在打别人结果自己的靶机被对手一个命令执行漏洞直接破了flag 被反复提交分数一泻千里。这份 CTF AWD 比赛工具收集.zip 我拆过一遍里面收的正是守方最常用的几样东西waf.php 请求拦截脚本、pyinotify 文件监控组件、WebLogAnalyse 日志分析工具、auto_submit_flag.py flag 自动提交脚本外加一套 Windows 下用的 D_Safe 网站安全组件。适合两类人一类是刚接触 CTF 入门、想快速搭起防守体系的选手另一类是已经打过几场 AWD、被对手用 Web 漏洞反复打进后台想补防守短板的团队。解压后能同时覆盖 Linux 和 Windows 两种靶机场景下面按我复现的路径一步步说。2. 拆包看家底四件套的分工与选型逻辑2.1 包内文件地图哪些给人用哪些给机器跑拿到压缩包后先别急着双击 exe先把这个包的目录结构理清楚。压缩包解开后不是单个工具而是好几套独立组件的合集我用一个表格把主要部分和它们在 AWD 里的职责列出来目录 / 文件类型在 AWD 里负责什么CTF-AWD-masterwaf.php、monitor.py、setup.py、setup.sh、README.md源码脚本集Web 层请求过滤 Linux 下文件变更监控 一键部署pyinotify / pyinotify-0.9.6pyinotify.py、monitor.py、pycPython 依赖库和脚本给 monitor.py 提供 inotify 内核事件能力监控文件增删改d_safe_2.1.5.4D_Safe_Manage.exe、Rule x32/x64、Modules、waf.phpWindows 平台程序网站安全狗旧版本负责 Windows 靶机的文件与 URL 防护WebLogAnalyseWeb日志安全分析工具 v2.0.exe、AttackRules.ini、qqwry.dat、NewTemplet.tpl日志分析工具包赛后 access.log 分析、攻击来源追踪、报告导出auto_submit_flag.py独立 Python 脚本自动抓取 flag 文件内容并提交到比赛平台weblogger / wupco_staticweblogpro.php、data.php、managelog.php、temp.php、install.php、rm_me.shPHP 脚本组比赛期间的 Web 日志管理、临时文件清理辅助这个结构其实很典型守方体系需要的是“拦截、监控、分析、提交”四个闭环缺一环都会在比赛中出问题。拦截靠 waf.php 和安全狗监控靠 pyinotify 盯着 Web 目录有没有多出恶意文件分析靠 WebLogAnalyse 在赛后把攻击路径摸清楚提交靠 auto_submit_flag.py 确保你拿到 flag 后能第一时间自动交分。weblogger 那组 PHP 文件更像是一个临时辅助工具箱用来做日志记录和清理残留。有一点要提醒d_safe_2.1.5.4 是网站安全狗比较老的版本压缩包里带了 Rule x32 和 Rule x64 两套规则说明它同时考虑到了 32 位和 64 位系统。但旧版本对新出的 webshell 变种识别有限不要把它当成主力 WAF更适合当作 Windows 靶机上的第二道保险真正的主角还是 waf.php。2.2 为什么偏偏是 pyinotify文件监控的选型对比AWD 比赛里文件监控的核心诉求是Web 目录下什么时候多了一个陌生 PHP 文件、什么时候 index.php 被改了你要第一时间知道。实现这个诉求有两条路一条是定时轮询一条是内核事件驱动。定时轮询的常见做法是写一个循环每分钟执行find /var/www/html -name *.php -mtime -1把结果和上次的快照做 diff。这种方式实现简单但有两个硬伤一是轮询间隔内攻击者已经完成上传执行提交你发现时 flag 已经被交走二是对高并发靶机来说每次全量 find 都会产生不少 IO打比赛时多开几个终端工具服务器负载很容易上来。pyinotify 走的是 Linux 内核 inotify 机制应用层注册关注的目录和事件掩码内核在文件发生创建、修改、删除、移动时主动通知不需要反复扫描。这个事件模型天然适合 AWD 的防守场景延迟低、无轮询开销。我用一个对比表把选型理由说清楚维度定时轮询pyinotify 事件监控发现延迟取决于轮询间隔通常 10 秒以上毫秒级内核主动上报IO 压力每次全量扫描目录只在事件发生时产生写入日志感知 rename/vim需要额外判断IN_MOVED_TO 直接捕获部署复杂度低但代码不省要装 pyinotify 库、写事件回调适用场景靶机数量多、监控粒度粗重点盯防的单机或少量机器这里有个容易被忽略的细节如果你在 AWD 里用的是 vim 改文件vim 保存时不会触发简单的修改事件而是先把内容写到临时文件再 rename。所以监控脚本如果只监听 IN_MODIFY就会漏掉这种最常见的篡改方式。我一般会把这个知识点同步到监控脚本的掩码配置里具体写法下一章展开。2.3 组件之间的依赖关系先装库再跑脚本包里的依赖关系不算复杂但顺序错了会发生“脚本运行报错找不到模块”的问题。CTF-AWD-master 下的 monitor.py 依赖同目录的 pyinotify.py而 pyinotify-0.9.6 是它的发布包。正确做法是先把 pyinotify 安装到 Python 环境再用 setup.py 或者直接手动把 monitor.py 放到靶机上运行。我在自己的复现环境里走的是这个顺序先python setup.py install装 pyinotify然后单独验证python -c import pyinotify确认导入成功后再跑 monitor.py。如果跳过安装直接跑大概率会在启动时被 ImportError 卡住新手容易误以为是脚本本身坏了。包里的 setup.sh 做的事情本质上也是这个流程只不过帮你把步骤写成了脚本我建议你先手动跑一遍理解链路再用它去批量部署。依赖还有一个隐性点pyinotify 只在 Linux 内核上生效Windows 靶机用不了。Windows 端要防护就靠 d_safe 这套安全狗监控思路也不一样它是基于文件系统过滤驱动的。所以拆这个包时要明确一个认知pyinotify 管 Linux安全狗管 Windows两者不要互换。3. 把 waf 落进靶机waf.php 部署姿势与规则调优3.1 部署前先做三件事备份、确认入口、确认运行模式拿到 waf.php 不要直接扔进 Web 目录就完事。我在实战里总结的经验是部署前必须确认三件事入口文件有哪些、当前 PHP 运行模式是什么、原文件的备份放哪里。先看备份。AWD 有攻防双方的实时对抗你改完配置后很可能被对手探测到异常所以原入口文件必须留一份完整的备份。我习惯把原始文件打包放到/root/backup/这种 Web 服务读不到的位置而不是放在/var/www/html里因为放到 Web 目录就等于把备份送给了对手。备份的打包命令很简单mkdir -p /root/backup tar czf /root/backup/www_original_$(date %Y%m%d).tar.gz /var/www/html/这个命令把整个 Web 目录压缩存档文件名带时间戳方便赛后还原。注意tar执行完成后要确认压缩包大小正常别出现打包到一半空间满了的情况。接下来是确认入口。一个 PHP 项目可能有多个入口index.php、admin.php、api.php甚至还有一些你没注意到的上传目录下的解析文件。waf.php 要能够覆盖所有入口不能只挂在 index.php 上否则对手开一个别的入口就能绕过拦截。最后确认运行模式是 Apache 还是 nginxphp-fpm。这决定了你用哪种方式挂载 waf.php配置路径完全不同。我的经验是先执行php -v、ps aux | grep php-fpm看一眼环境再决定走哪条配置路径。3.2 用 auto_prepend_file 挂载 waf.php 的两种路径把 waf 挂在所有 PHP 请求前的标准技术方案是auto_prepend_file意思是让 PHP 在每个脚本执行前自动先加载指定的文件。这个机制比修改每个入口文件的 include 要干净得多好处是隐蔽、不易被对手逆向 diff而且一处配置全局生效。如果你面对的是 nginx php-fpm最直接的路径是改 php.iniauto_prepend_file /var/www/html/waf.php改完之后要重启 php-fpm 服务才能生效systemctl restart php-fpm这里有个参数顺序的坑如果你在 php.ini 里同时写了多个 PHP-FPM 池的配置有的池子会覆盖主配置。重启完不是结束一定要执行下面这个命令确认配置真正被加载了php -i | grep auto_prepend_file执行结果里如果显示的是你写的绝对路径说明挂载成功如果显示为空说明配置被覆盖需要去/etc/php-fpm.d/下的池配置里再检查一遍。如果你面对的是 Apache可以用 .htaccess 来做不需要改全局配置php_value auto_prepend_file /var/www/html/waf.php这条配置放在站点根目录的 .htaccess 里仅对当前目录及其子目录生效。优点是灵活可以给不同站点挂不同的 waf 规则缺点是如果你不熟悉 Apache 的 AllowOverride可能遇到配置被 .htaccess 忽略的情况因为服务器没开启 AllowOverride All。遇到这种情况执行curl -I看响应头如果请求能正常返回 200 但 waf 拦截逻辑没生效多半是 .htaccess 没被读取。3.3 规则调优把 AttackRules.ini 的思路搬进 waf 配置waf.php 里真正决定拦截效果的不是 PHP 代码本身而是它加载的那套规则。实战中我会参考包里 WebLogAnalyse 的 AttackRules.ini 思路来设计 waf 规则因为日志分析工具能命中哪些攻击特征反过来就说明哪些特征在靶机上真实出现过。AttackRules.ini 是 INI 格式的规则文件本质上是把攻击特征按规则分组常见结构这样理解[SQL_Injection] match_type regex pattern (select.*from|union.*select|sleep\() action BLOCK [Command_Exec] match_type keyword pattern system|exec|passthru|shell_exec|proc_open action BLOCK这个文件我理解为四列信息规则段名、匹配类型、匹配内容和处理动作。match_type可以按正则匹配也可以按关键词匹配action除了 BLOCK 还能配成 LOG用来先观察再决定是否拦截。把它搬到 waf.php 里的核心原则是先记录后拦截最后再收紧不要开局就把正常业务打死。我踩过一次很典型的误杀比赛平台本身有个功能是查询用户信息参数里带了select这个单词waf 规则写的pattern select直接命中整个页面白屏。后来我把规则的match_type和pattern改细规定必须满足select.from这种完整结构才拦截误杀率立刻降下来。还有一个调优点在规则顺序上。waf.php 处理规则如果是顺序匹配那么“放行白名单”必须排在“拦截黑名单”前面否则特定用户的请求永远轮不到放行。我一般会在规则数组最前面插入一组白名单配置把比赛平台的健康检查接口、静态资源目录直接放掉剩下的请求再走黑名单检查。这样配置之后WAF 才能做到主要拦命令执行和 SQL 注入又不影响平台正常可用性。4. 文件监控与 flag 提交两段可以直接改的代码4.1 monitor.py 核心逻辑与掩码参数AWD 里文件监控脚本的目标很明确在 Web 目录的文件发生变化时把变化内容记到日志最好还能自动做备份。包里的 monitor.py 基于 pyinotify 实现我把核心逻辑写成一个可以直接在你靶机上跑的最小版本注释写清楚import pyinotify import os import time WATCH_DIR /var/www/html # 要监控的 Web 目录 LOG_PATH /tmp/awd_monitor.log # 监控日志输出 INTEREST_EXTS (.php, .jsp, .asp, .sh, .py) # 重点关注文件类型 class AwdHandler(pyinotify.ProcessEvent): def process_IN_CREATE(self, event): self.handle(event, CREATE) def process_IN_MODIFY(self, event): self.handle(event, MODIFY) def process_IN_DELETE(self, event): self.handle(event, DELETE) def process_IN_MOVED_TO(self, event): # vim 和部分编辑器保存文件时是先写成临时文件再 mv 过来 self.handle(event, MOVED_TO) def handle(self, event, action): path os.path.join(event.path, event.name) if event.name.endswith(INTEREST_EXTS): line %s [%s] %s % (time.strftime(%Y-%m-%d %H:%M:%S), action, path) with open(LOG_PATH, a) as f: f.write(line \n) wm pyinotify.WatchManager() handler AwdHandler() notifier pyinotify.Notifier(wm, handler) mask ( pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE | pyinotify.IN_MOVED_TO | pyinotify.IN_ATTRIB ) wm.add_watch(WATCH_DIR, mask, recTrue) notifier.loop()这个脚本的核心是mask掩码它决定了内核会向你的程序推送哪些事件。很多人只写IN_CREATE和IN_MODIFY结果 vim 保存文件时发现没记录就是因为漏了IN_MOVED_TO。开发环境里编辑器保存文件的操作通常是“先写临时文件再覆盖目标文件”这个覆盖动作在内核层表现为 rename对应的事件就是IN_MOVED_TO。加上它之后我监控到的篡改事件数量明显变多。另外一个参数是recTrue。它表示递归监控子目录因为 Web 项目的 PHP 文件可能分布在多层子目录里。但要注意递归监控会消耗更多文件描述符如果你的靶机比较弱建议只监控你确认有 PHP 文件的目录而不是整个网站根目录否则大量静态资源的读写事件会把日志刷爆。这个脚本跑起来后记得用nohup放在后台运行nohup python monitor.py /tmp/monitor.out 21 运行后用下面的命令验证是否真的在工作向监控目录写一个测试文件然后看日志有没有记录。touch /var/www/html/test_monitor.php tail -n 5 /tmp/awd_monitor.log如果日志里出现了[CREATE] /var/www/html/test_monitor.php说明监控链路通了。记得最后把测试文件删掉它本身也是攻击者的诱饵。4.2 auto_submit_flag.py 改造适配带 Cookie 的接口包里的 auto_submit_flag.py 实现的是最基本的“读 flag 文件正则提出来POST 到比赛平台”。但很多比赛平台要求的不是裸 POST而是带登录 Cookie 或 token 的请求。我一般会把脚本改成 requests.Session 方式先把会话登录态保持住再提交 flag。下面这段代码可以作为一个改造模板import requests import re import time API_LOGIN http://10.0.0.2/api/login API_SUBMIT http://10.0.0.2/api/flag USERNAME your_team PASSWORD your_password FLAG_FILE /tmp/flag.txt def load_flag(): with open(FLAG_FILE, r, encodingutf-8) as f: content f.read() match re.search(rflag\{[^}]\}, content) return match.group(0) if match else None def get_session(): session requests.Session() login_data {username: USERNAME, password: PASSWORD} resp session.post(API_LOGIN, datalogin_data, timeout5) if resp.status_code ! 200: raise RuntimeError(login failed: %s % resp.status_code) return session def submit_flag(session, flag): resp session.post(API_SUBMIT, data{flag: flag}, timeout5) return resp.status_code, resp.text def main(): session get_session() while True: flag load_flag() if flag: flag flag.strip() code, text submit_flag(session, flag) if code 200 and success in text.lower(): print(submit ok:, flag) break else: print(submit failed:, code, text[:80]) time.sleep(3) if __name__ __main__: main()代码里值得注意的两个参数一是load_flag()里的正则flag\{[^}]\}它默认兼容常见的flag{...}格式。但有些比赛平台自定义了格式比如用ctf{...}或者hdn{...}这个正则要同步改改法就是用平台给定的前缀替换flag部分。二是在submit_flag之前我做了flag flag.strip()专门去掉从文本文件读出来的换行符\n。很多新手脚本死活提交失败最后发现就是 flag 字符串带了\r\n平台比对时永远不相等。轮询间隔time.sleep(3)是 3 秒这个值要看平台提交频率限制来调。有段时间我为了抢速度把间隔改到 0.5 秒结果平台服务端直接把我判成“异常提交”扣了分。一般情况下 3 到 10 秒的间隔都不会引起平台注意稳妥起见放到 5 秒既能保证第一时间交分又不会触发频率限制。5. 避坑实录这套工具链的五次翻车与排查5.1 WAF 挂载后全站 502现象在 nginx php-fpm 环境里配好auto_prepend_file并重启服务后整个站点全部 502浏览器直接报网关错误。原因最常见的两种情况一是auto_prepend_file的路径写成了相对路径php-fpm 的工作目录和网站根目录不一致导致 PHP 加载 waf.php 时找不到文件二是 waf.php 本身有语法错误PHP 在预加载阶段直接崩溃。很多人的路径是复制过来的里面带着~这样的用户目录符号nginx 和 php-fpm 根本不会展开。解决把路径改成绝对路径并且执行前先做语法检查。先用php -l /var/www/html/waf.php确认没有语法错误再用php -i | grep auto_prepend_file确认加载路径正确。改完配置后先只重启 php-fpm再 curl 测试一个动态页面不要一次动多个配置否则排查起来到处都是嫌疑人。5.2 monitor.py 不触发现象监控脚本运行起来了进程在但往 Web 目录里写一个 PHP 文件日志里没有任何记录。原因我之前说过第一层原因就是掩码漏了IN_MOVED_TO编辑器保存文件触发的不是IN_MODIFY。第二层原因是目录路径配错监控的和实际写入的目录不是同一个物理目录比如 Web 站点做了软链接你监控的是真实路径但攻击者写的是软链接路径事件不会触发。解决在掩码里加上IN_MOVED_TO和IN_CLOSE_WRITE然后用真实路径去监听。先执行readlink -f /var/www/html解析出物理路径再把这个物理路径写入WATCH_DIR。改完之后重新跑脚本用touch和mv两步测试确认CREATE和MOVED_TO都能记录。5.3 安全狗误杀正常请求现象在 Windows 靶机上部署 d_safe 之后平台本身的查询功能开始报错接口虽然没有白屏但返回数据异常。查 WebLogAnalyse 日志看到正常业务参数被标记成攻击。原因安全狗自带的 SQL 注入规则匹配范围比较宽凡是参数里带select、from这种关键词的请求不管是不是真实注入都会被拦。比赛平台很多业务功能本身就是靠查询数据库实现的参数里带这些词很正常。解决把攻击规则的默认动作从“拦截”改成“记录”先观察比赛前几轮哪些规则经常误命再按 URL 做白名单。在 D_Safe_Manage.exe 的管理界面里找到 URL 白名单配置把平台自己的查询接口路径加进去。如果具体规则支持正则就把select改成select.from.(information_schema|sys)这种带明显攻击意图的组合尽量避免裸关键词拦截。5.4 flag 提交一直无效现象auto_submit_flag.py 跑起来了脚本也提示请求已发出但平台一直返回invalid flag或submit failed分数纹丝不动。原因这个坑有两种变体。第一种平台要求请求必须携带登录 Cookie我最早写脚本时直接requests.post裸请求没有先登录平台根本认不出是谁在提交。第二种flag 文件里提取出的字符串带着换行符或不可见字符平台比对 flag 内容时失败。解决用requests.Session()先调登录接口把登录态保持在同一会话里再用这个 session 做后续提交。提取 flag 之后强制.strip()去掉换行和空格。如果不能确定平台返回的“成功”关键词是什么就把text内容打印出来看一次再修改判断条件。记住一个原则脚本的提交成功逻辑要以平台实际返回为准不要想当然用 HTTP 200 当唯一标准。5.5 日志工具脱离目录就报错现象我把 WebLogAnalyse 文件夹里的 exe 单独复制到桌面运行结果提示找不到模板或者 IP 库文件界面直接停住。原因这个分析工具读取NewTemplet.tpl和qqwry.dat时用的是相对于当前工作目录的路径而不是相对于 exe 所在目录的路径。你把 exe 单独拿走它自然找不到同级的模板和 IP 库。解决不要拆散这个工具包。运行时保持Web日志安全分析工具 v2.0.exe、AttackRules.ini、qqwry.dat、NewTemplet.tpl四个文件在同一目录下。如果在比赛中想分析多个服务器的日志把日志文件复制到这个目录下再操作不要反过来把 exe 丢进日志目录。这个坑看起来小但真到了赛后复盘时很耽误时间。6. 赛后复盘把 WebLogAnalyse 当成还原攻击路径的显微镜AWD 比赛结束后的第一个动作不是关靶机而是把日志完整拖下来。用 WebLogAnalyse 复盘时我把它当成一只显微镜来看攻击路径操作流程是固定的先停掉 Web 服务的写入接着把 access.log 和 error.log 打包拉到本地然后用这个工具做规则命中分析。使用步骤上没有代码但有几个参数需要注意。打开Web日志安全分析工具 v2.0.exe后第一步选日志文件位置第二步选AttackRules.ini作为规则库第三步设置分析时间范围建议把时间范围设成整场比赛时段而不是当天避免漏掉跨天的请求。点击分析后工具会用规则库里的特征去匹配每一条请求把命中的记录单独标出来。我用一个表格列出复盘时重点看的维度分析维度关注的日志字段目的是什么攻击来源 IPremote_addr找出高频扫描者反查是自己人还是外部队命中规则匹配的 AttackRules 规则名判断对手主要用了 SQL 注入还是命令执行Payload 完整性request_line、args还原攻击者实际提交的恶意载荷时间序列request_time、timestamp还原攻击者的试探和利用时间线返回状态status判断哪些攻击真正打进来哪些被 WAF 挡住这里有一个我自己的血泪教训有一场比赛打完我急着清理靶机环境把日志文件随手删了。赛后复盘时想确认对手是不是使用了某个命令执行 payload翻遍所有备份都找不到最后只能靠记忆复盘结论自然不可靠。从那以后我每次 AWD 收尾都强制走一遍固定流程先systemctl stop nginx停服再打包 access.log 和 error.log最后才允许清理环境。日志不拖出来任何 WAF 和监控脚本都只是一次性的防护没办法变成下一次比赛的防守经验。如果你也想把这套工具链用好建议你先把 WebLogAnalyse 在赛前拿自己的攻击测试流量跑一遍确认它能命中你已知的攻击特征而不是等赛后才第一次打开它。工具有没有生效赛前验证一次比赛后猜十次都管用。希望这个拆包笔记帮到你。本文还有配套的精品资源点击获取
返回列表