1. 开赛前两小时:赛制信号与目标赛题摸底
AWDP这种模式,和纯CTF的解题赛完全是两种打法。解题赛你可以按自己的节奏,先挑软的捏,慢慢磨难题;AWDP的核心逻辑只有三个字:抢时间。你比对手早一分钟拿到flag,就多一分主动权;你比对手晚一分钟回去补漏洞,就可能在分数榜上被拉开一个身位。
半决赛那天早上到场地的时候,我先做的不是翻规则文档,而是把整场比赛的计分逻辑重新捋了一遍。AWDP的典型结构一般是:主办方给每个参赛队下发一套完全相同的Web靶机环境,除了初始防护策略可能略有差异外,漏洞点基本一致。你的得分来源分三块:一是每道赛题预设的flag,在固定时间点刷新,你需要在刷新后尽快通过漏洞打进去拿到flag提交;二是你攻破别人靶机后,可以植入指定的"破坏性文件"或者篡改页面内容,按存活时间和范围判分;三是你守好自己的靶机,阻断别人打进来,防御行为本身也有一定的基础分。
这里有个很多人容易忽视的点:AWDP是同一套环境,意味着你发现一个洞,所有队伍的环境理论上都有同一个洞。 所以前半程的核心不是"会不会做这道题",而是"能不能在别人把洞堵上之前把分拿到"。半决赛前几分钟,我看到下发靶机的端口开放情况是典型的Web服务集合——80端口的业务站点、3306端口的MySQL、6379的Redis暴露在外网,还有一个不太常见的9000端口,后来确认是内网使用的目录浏览服务。这种暴露面越大,说明主办方故意留的口子越多,考察的不是单纯的漏洞挖掘,而是你在多个口子之间的优先排序能力。
我当时的初始判断是:这一场半决赛的赛题风格偏向"复杂业务逻辑+已知组件漏洞组合利用",不太可能出那种看一眼就知道是CMS几行代码注入的题目。接下来的比赛节奏也印证了这一点——真正拉开差距的地方,往往在一个你忽略的次要端口,或者一套老组件里埋着的二次开发代码。
2. 半决赛实战拆解:三道最具代表性的赛题复盘
2.1 第一道题:老版本框架反序列化点与本地文件读取的组合利用
第一道题的业务场景是一个类似"内部工单系统"的PHP应用。打开首页,登录框、工单列表、附件下载,功能倒是齐全,但整套系统的款式一眼就能看出是拿几年前流行过的一套开源框架改的。这类题的典型套路是:开发者在原框架之上做二次开发,结果把框架自带的调试组件暴露了出来。
我现在的习惯是,拿到一个Web题先做三件事:
- 看响应头里带的服务器版本、框架标识、X-Powered-By信息;
- 翻静态资源路径,看有没有已暴露的JS、图片、样式文件可以反推目录结构;
- 扫一遍常见备份文件、调试接口、路由配置文件。
这道题在第二步就有了收获——站点根目录下一个debug.php.bak文件直接暴露了调试组件的启用状态。现在的比赛环境虽然不会让你那么轻松拿到源码,但这个备份文件里恰好写了一个反序列化入口,参数名是data,接收base64编码后的数据,直接传入一个没有做任何过滤的unserialize()函数。
当时我用了两条路线并行:
- 一条路线是拿已知的PHP框架反序列化POP链打RCE;
- 另一条路线是利用这个入口做文件读取,先拿
/etc/passwd验证执行路径,再读取Web目录下的配置文件。
最后走的实际上是第二条路线。原因是主办方给这个入口加了一层disable_functions,把system、exec、passthru这些常见命令执行函数全禁掉了。你就算拿到了RCE权限,也执行不了命令,只能做文件读写和目录遍历。这时候反而变成了一道"受限条件下的文件利用题"。
解题的关键动作变成了:
- 通过反序列化入口读取
/var/www/html/config/database.php,拿到MySQL账号密码; - 用PDO的MySQL连接能力,尝试
select ... into outfile写Webshell; - 如果写不进Web根目录,就换个思路——查找系统里有没有定时任务、SSH密钥、可写的启动脚本等。
实测下来,这个环境里secure_file_priv设的是空值,允许任意目录写文件,直接一条SQL就把一句话木马写到了Web根目录下。拿到Shell之后的首要任务不是立刻提交flag,而是先看数据库里有没有目前阶段的flag,如果有就直接入手,省得等刷新时间。
这道题的经验价值在于:遇到disable_functions限制时不要慌,文件读写能力在AWDP里很多时候比RCE更好用。因为比赛环境的flag最终是要靠你拿到shell之后,在指定目录下读取文件才能提交的,RCE只是手段,文件读取才是目的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2.2 第二道题:文件上传的二次渲染绕过与日志投毒
第二道题是一个"设备在线报修系统",核心功能模块是用户上报故障、上传现场照片、管理员后台审核。这套系统在用户端上传文件时不校验内容,只校验扩展名和MIME类型,听起来很简单对吧?但问题出在管理员后台有一个图片二次渲染功能。
我找到的完整利用链是这样的:
- 用户端上传一个
jpg文件,内容实际上是一段PHP代码,扩展名改成.jpg; - 普通上传接口不检查文件内容,成功落盘;
- 管理员后台有个"图片修复"功能,会对所有图片调用一次
imagecreatefromjpeg()和imagejpeg()重新保存; - 二次渲染过程中,图片的EXIF信息和部分文件尾部内容会被保留,如果你把PHP代码藏在文件尾部恰当的位置,渲染后那段代码仍然会被保留在合法图片数据之后。
这时候只要找到一个"包含文件"的操作点就行。这套系统的详情页在展示图片时,有一个历史遗留的参数?page=item&img=uploads/xxx.jpg,文件内容被引入时没有做过任何过滤。
把两步拼起来,就形成了一个稳定的Webshell:上传含马图片 → 触发后台二次渲染 → 访问详情页图片路径 → 代码被执行。
这道题实际走得比预想中顺利,因为我绕过文件头校验时,直接用了含有一张正常JPEG图片头和尾部追加了短标签<?php @eval($_POST['x']);?>的内容。imagecreatefromjpeg正常读取图片头信息生成新图,后半段的PHP代码不在图片结构解析范围内,但再次被include时又会被PHP引擎当作脚本执行。
这里有一个AWDP特有的大坑,当时差点翻车——你打进自己的靶机拿到Webshell之后,一定要立刻把上传入口的校验逻辑补上,否则同一时间对手也在扫描同一个上传接口,谁先打进去谁就拿到了主动权。 我当时是先花了几分钟写了一个简单的扩展名白名单和文件内容magic bytes检查,覆盖到原上传接口,然后再回头慢慢打第二轮flag。补防永远排在二次利用之前,这是AWDP里一条铁律。
2.3 第三道题:9000端口暴露的目录服务与内网横向链路
这道题是整场半决赛里我觉得设计得最有水平的,也最考验对"非标准攻击面"的敏感度。
9000端口上跑的是一个轻量级的目录浏览服务,界面和服务本身都有点像某些团队内部自研的"网盘共享系统"。这个系统存在两个非常明显的经典漏洞:
- 第一个是路径穿越,访问
/download?file=../../../../etc/passwd可以直接读到系统文件; - 第二个是未授权访问,
/admin目录没有做任何权限验证,直接暴露了一个可执行命令的"系统诊断"功能。
利用路径穿越先拿到Web应用配置文件后,我确认了这套服务是以www-data用户权限运行的。接着用未授权的命令执行功能读取了当前网络配置,发现内网网段是172.16.8.0/24,而我的靶机Web应用所在的网段是172.16.1.0/24。
这时候思路就变得清晰了——这一题考的不是单点突破,而是内网横移。
我做了下面几步:
- 用命令执行功能探测内网存活主机,写了一个简单的
for循环批量ping,发现172.16.8.10-20段有十余台主机在线; - 扫描常见端口,发现
172.16.8.15这台主机上开放了3306; - 尝试用之前从Web配置里拿到的MySQL账号密码做复用,发现
root/root@2026直接登录成功; - 查询数据库,发现一个
flag_store表,里面存着一串疑似flag的字符串。
但提交之后没有得分。原因在于比赛的flag会定时刷新,我这个时间点拿到的可能是一个"测试态"的字符串,也可能是被其他队伍抢先提交导致这个当前周期flag失效。后来换了思路,直接在数据库里翻users表,找到了真实flag。这提醒了我一个经验:AWDP里数据表里存的字符串不一定全是flag,有些是干扰项,有些是过期项,要结合提交结果来判断哪个是"活的"。
这道题还有一个需要特别说明的地方:目录服务通常没有Web应用那么高的曝光度,很多队伍在打前两道题时会忽略9000端口。实际上在AWDP里,所有非标准端口都值得花两分钟探测一下,因为主办方既然开了这个端口,就说明这里有分。
3. 防守得分的细节:加固流程、WAF规则与文件监控布防
很多第一次打AWDP的选手会有一个误区:防守就是堵漏洞、改代码、删文件,把洞补上就完事了。实际上AWDP的防守得分逻辑要复杂得多。你不仅需要防住别人的攻击,还要让比赛平台"看到"你做了有效的防护操作,并且在对手攻击你的时候能"留下记录"作为证据。
半决赛的防守题我整理出来的有效操作顺序是:
- 用备份文件快速恢复一份"最初始状态"的Web目录到临时文件夹,做差异比对,确认主办方预置了哪些漏洞文件和后门文件,这样可以快速定位到漏洞点;
- 对Web目录做一次全局扫描,找出所有近期创建或者近期修改的文件,这些往往是主办方埋的洞,或者上一轮对手打进来留下的马;
- 统一修改数据库口令、应用配置里的密钥,同时把
phpMyAdmin这类管理工具入口关掉或加上访问限制; - 给所有入口页面加一个简单的WAF规则层,采用白名单+黑名单双保险,既拦截常见的
select、union、information_schema关键词,也拦截所有非必要的请求方法; - 开启文件监控,对Web目录的写入操作记录日志,并设置一个每30秒执行一次的后台脚本,轮询找出新增文件后自动删除。
第4步和第5步是整个防守得分的核心。
WAF规则我采用的是直接在nginx配置里加if ($request_uri ~* "...") { return 403; }这种方式,简单粗暴,但效果非常好。黑名单词表里我重点放了以下几类:
- SQL注入的常用函数:
union select、sleep、benchmark、load_file、into outfile; - 文件相关操作:
../路径穿越、php://filter、data://; - 一句话木马特征:
eval(、assert(、base64_decode、$_POST、$_GET、GIF89a; - Webshell管理工具特征:
antSword、Behinder、godzilla等流量特征。
文件监控脚本我写的是一个最简单的PHP脚本,遍历Web目录下所有文件,用filemtime()对比最近一次扫描时间,如果发现有新增文件或者被修改的.php文件,就立刻记录日志并删除。这里有一个细节:删除操作最好放在脚本轮询里,而不是用inotifywait实时监控,因为比赛环境负载很高,实时监控容易漏事件。
补洞的顺序也是有讲究的。优先处理已知漏洞入口,但不要直接删掉漏洞模块,因为删了之后对手虽然打不进来,但你的攻击分也少了——很多队伍的防御策略是"你先别堵死,等我把对手引流到这个假漏洞上,再一网打尽"。我在实际比赛中是留了两个看似没补的"蜜罐入口",实际上在里面埋了记录攻击源IP的脚本。每当有对手尝试访问这个入口时,我的日志就会记录下他们的IP、User-Agent、攻击payload,后续如果再打他们靶机,就多了一份情报支撑。
当然,AWDP的半决赛里时间有限,蜜罐这种策略属于"锦上添花",如果你前期的洞还没补完,就别急着做蜜罐,本末倒置是大忌。
4. 险情处置记:一次被判超时的应急链路全回溯
半决赛进行到大约第三个小时,发生了一次差点让我心态崩了的意外。运行中的靶机Web服务突然返回502,刷新页面直接连不上,平台判定我的服务"失联"并开始扣存活分。
我当时的第一反应不是重启,而是先确认原因。我迅速执行了以下链路排查:
- 先确认本机到靶机的网络通不通,执行
ping和telnet 靶机IP 80测端口; - 端口通,但HTTP请求返回502,说明Web服务进程大概率还活着,是后端的
php-fpm或数据库连接出了问题; - 登录服务器看进程状态——
ps aux | grep php发现php-fpm还剩几个进程,但日志里刷满了数据库连接失败的报错; - 用命令行客户端连MySQL,发现
mysqld进程毫无响应,show processlist也卡死,判断是数据库服务假死; - 查看系统负载后发现
load average飙到了30多,swap几乎耗尽,内存碎片化严重,/tmp目录下有大量可疑的可执行文件。
到这一步基本可以断定,是有对手往我的靶机上传了批量执行脚本,通过Webshell不断创建进程消耗系统资源,想把我的服务拖垮。这是AWDP里非常常见的一种攻击思路——不直接拿flag,而是搞资源耗尽,让你没时间攻、没分守。
我采取的处理方式是先斩断攻击源,再恢复服务,最后清理后门:
- 第一时间杀掉所有
www-data用户下可疑的PHP进程,pkill -u www-data -9; - 重启
php-fpm和mysqld,先恢复业务响应,稳住存活分; - 删除
/tmp下所有近期创建的可执行文件; - 检查Web目录,把最近10分钟内创建的所有
.php文件全部清掉; - 修改数据库账号密码并立即重启MySQL,让对手已经建立的连接和已落地的后门失效。
整个处置过程大概花了6分钟,期间被平台扣掉了两轮存活分。这个代价不算小,但重要的是让我把"资源耗尽攻击"的处理流程完整跑通了一遍。这比单纯看文章要直观得多。
事后复盘这次险情,我总结出三个教训:
- 第一,防守并非只盯着Web目录就够了,系统的
/tmp、/var/tmp、/dev/shm这些可写目录同样是攻击者的目标。比赛到后半程,我每隔十分钟都会检查一下这些目录的新增文件,把它们当作Web目录一样监控起来。 - 第二,数据库口令必须第一时间改掉。当时我还没改MySQL密码,对手就是通过之前那个反序列化漏洞读取到的配置文件里拿到了数据库口令,然后借助数据库
into outfile写入马。如果我再早一点改掉库密码,后门的写入路径就断了一半。 - 第三,要有快速的流量分析能力。事后我抓取了一段网卡流量,用
tcpdump过滤出HTTP POST请求,逐个查看payload。结果发现对手的Webshell管理工具流量特征非常明显,甚至直接在POST数据里带了assert、eval这些敏感函数名。如果在WAF规则里把这些关键词也加进去,即使马已经落地了,他们也连不上。
处置完这波攻击之后,我没有急着回去继续刷分,而是用剩余时间做了一次全局的"二轮加固"——把之前临时防护规则升级成更严格的版本,删除所有可能的反弹Shell入口,确保后续不再出现服务宕机的情况。这种"先稳后攻"的策略,在后面两轮flag刷新时帮我省去了很多来回切换的精力。
5. 赛后复盘:决赛前最有价值的几项准备
半决赛结束之后,名次也还算理想,但真正值钱的不是那个结果,而是整场比赛过程中试出来的经验和暴露出的短板。如果要给接下来的决赛列一个准备清单,我会把下面这几件事排在优先级最高的位置。
5.1 工具链的完整性和离线可用性
AWDP比赛现场的网络环境通常是隔离的,这在半决赛时差点坑了我。当时我需要一个反序列化利用工具,结果发现现场环境里没有现成的,线上的GitHub仓库也连不上,只能靠手工拼Exp。那种手写代码的压力,和平时在本地慢慢调完全不是一个体验。
决赛前我会把以下这些工具全部打包好:
- 一个完整的Web目录备份文件集,包括各类型的主流CMS和框架(PHP、Java、Python)源码,方便快速做代码审计;
- 本地化漏洞利用脚本库,包含反序列化链、文件上传绕过、SSTI表达式、SQL注入绕过、定时任务提权等各类EXP模板;
- 各种Webshell管理工具的本地副本,以及对应的加密流量解密脚本;
- 一个离线版的目录扫描字典,包括常见的备份文件、管理后台、配置文件路径。
5.2 加固脚本的模板化沉淀
这次半决赛里,我的加固操作很多都是现场临场敲的,浪费了不少时间。赛后我整理了一份通用的加固脚本模板,决赛开赛后直接往靶机上一扔,几秒钟就能完成基础防护:
- 自动修改Linux系统账号密码;
- 自动修改MySQL、Redis等服务的认证方式;
- 自动给Web目录加上只读权限,仅开放必要的上传目录可写;
- 自动部署一个支持规则热加载的简易WAF层;
- 自动开启文件监控和关键词拦截;
- 生成一份初始文件哈希表,后续增量比对可以快速发现新写入的文件。
这套模板的价值不在于它有多高级,而在于它把"人肉操作"变成了"固定流程",让我能把更多精力投入到进攻侧。
5.3 对抗经验的心理准备
打完半决赛我最大的感受是:AWDP的比赛节奏特别考验人的判断力,很多决策必须在几十秒内做出。比如你刚打进对手靶机拿到一个Webshell,下一秒发现另一个队伍的队伍已经先一步改了文件权限,你怎么办?是换路径再试,还是先去补自己靶机的洞?这种取舍没有标准答案,但比赛经验能显著提升你的反应速度。
还有一个体会是:比赛过程中千万不要因为一次失误就乱了节奏。我当时数据库假死那一次,从发现问题到恢复服务,前后六分钟,期间平台一直在扣存活分,心理压力非常大。但在处理完之后我没有急躁地冲回去进攻,而是冷静地把防守补完、把日志留住、把攻击源IP记下来。事实证明这种稳定性比短暂的高爆发更能帮助拿分。
6. AWDP实战中那些不会写进官方文档的习惯
最后聊几条纯粹靠实战积累出来的习惯,不算什么高深技巧,但在现场特别管用。
习惯一:每台靶机开一屏终端窗口。 我打AWDP的习惯是给每个正在攻的靶机单独开一个终端标签页,命名直接用靶机IP,里面保存着当前的利用进度、已拿到的凭据、下一步计划。这样即使你同时拿着三个靶机,也不会因为忘了哪个进度而前功尽弃。
习惯二:所有密码用统一的密码本保存。 比赛期间你会接触到越来越多的数据库口令、后台口令、服务器账号。我见过太多选手打着打着忘了自己的密码,被迫再去翻命令行历史。我在本地放着一个加密的密码本文件,每拿到一个新的凭据就立刻记下来,同时也在本子上做标记,方便随时查阅。
习惯三:拿flag的第一时间提交,而不是攒到最后。 这一点看着像废话,但实际操作中很多人会陷入"多打一个洞再一起提交"的心态。比赛中每个flag的有效期是有限的,宁可分批次多提交几次,也不要等所有洞都打完再一次性提交,风险太大。
习惯四:留一手备用通道。 每打进一个靶机,我都会在确认安全的前提下,给自己留一个备用通道入口。这个入口不一定用上,但一旦主入口被封,备用通道能让你节省再次突破的时间。当然,前提是不违反比赛规则、不做危害平台的额外操作,只是保留自己的合法利用路径。
这些习惯单独拿出来都不值一提,但当它们组合在一起,就成为了一场AWDP比赛中相对稳定的发挥基础。半决赛已经翻篇,决赛场上是全新的环境和全新的对手,唯一能依靠的只有平时训练和临场稳定的发挥。希望这篇复盘对同样在备赛的朋友们有些帮助,也预祝大家在决赛里都能打出自己的节奏。
