拓冰建站拓冰建站
首页 / 资讯中心 / 正文

PHP文件包含漏洞实战:从Warmup题解析伪协议利用与防御

1. 项目概述从一道Warmup题看PHP文件包含的攻防博弈最近在带新人入门CTF发现很多朋友在Web安全赛道的起点——Warmup题目上就卡住了。这类题目通常设计精巧旨在考察一个核心漏洞点的基本利用而PHP文件包含漏洞就是其中最经典的“敲门砖”之一。它不像SQL注入那样需要复杂的闭合与绕过也不像命令执行那样直接粗暴文件包含更像是一把精巧的“万能钥匙”通过服务器对文件路径的信任去开启一扇扇本不该开启的门。今天我们就从一个典型的Warmup题目出发彻底拆解PHP文件包含漏洞的实战利用链条。无论你是刚接触CTF的新手还是想巩固Web安全基础的老兵这篇文章都将带你从“看到题目一脸懵”到“理解每一步操作背后的逻辑”真正掌握这种漏洞的攻防精髓。2. 核心漏洞原理与PHP伪协议武器库2.1 文件包含漏洞的本质信任的滥用文件包含漏洞的核心源于开发者在编写代码时将用户可控的输入直接或间接地传递给了文件包含函数如include、require、include_once、require_once。在PHP中这些函数的本意是提高代码复用性比如根据用户选择的语言加载不同的模板文件include($_GET[lang] . .php)。问题就出在这个$_GET[lang]上。攻击者可以传入的远不止en或zh。他们可以传入../../../etc/passwd尝试读取系统文件或者传入php://input并附上恶意代码让服务器执行。服务器之所以会照做是因为它“信任”了开发者设定的逻辑路径却没有对用户输入进行严格的“身份验证”和“路径限制”。这就好比你把家门钥匙藏在脚垫下本意是方便自己却让所有知道这个秘密的人都能随意进出。在CTF中这类漏洞的利用通常分为两个层次本地文件包含LFI和远程文件包含RFI。LFI是指包含服务器本地文件系统上的文件而RFI则能包含远程服务器攻击者控制上的文件。由于现代PHP默认配置allow_url_includeOff通常禁用了RFI因此CTF题目和真实环境中更常见的是LFI。我们的挑战就在于如何在只能包含本地文件的前提下实现信息泄露甚至代码执行。2.2 PHP伪协议将文件包含转化为攻击利刃如果只能包含.php文件那漏洞的危害将大打折扣。PHP内置的众多封装协议Wrapper Protocols俗称伪协议彻底改变了游戏规则。它们允许我们以“协议”的方式去访问各种输入/输出流在文件包含的上下文中这成了攻击者的强大武器库。下面我们重点剖析几个在CTF中最常出镜的“明星协议”。php://filter文件内容读取器这是LFI漏洞中用于读取源码的“神器”。它的核心作用是给数据流套上一层或多层“过滤器”。在文件包含时我们可以用它读取目标文件的原始内容并以编码如Base64的形式输出从而绕过源码直接执行导致的空白页问题。?filephp://filter/readconvert.base64-encode/resourceflag.php这行Payload的意思是使用php://filter协议应用convert.base64-encode过滤器去读取resource指定的flag.php文件。服务器会执行包含操作但由于我们要求将文件内容Base64编码后再包含PHP引擎会先读取flag.php的源码进行编码然后将编码后的文本而非可执行的PHP代码作为内容输出。这样我们就得到了flag.php的Base64编码字符串解码后即可查看源码寻找flag。注意read参数在某些情况下可以省略因为php://filter默认的处理模式就是读取。但明确写上是个好习惯也能帮助你理解数据流的方向。php://input直接代码执行通道这个协议允许你访问请求的原始主体raw POST data。当它被用于文件包含时服务器会将POST过去的数据当作PHP代码来执行。GET /?filephp://input POST ?php system(ls);?这里file参数指定了包含php://input流。紧接着在POST数据体中我们直接写入PHP代码system(ls);。服务器在包含这个“文件”时实际上执行了我们POST的代码。这是一种非常直接的“无文件”代码执行方式但前提是allow_url_include配置需要为On在部分CTF题目中会特意开启此配置来考察。data://内联代码注入器data协议可以将一段定义好的数据内联为文件流。它非常适合用于注入简短的代码片段。?filedata://text/plain,?php phpinfo();? // 或者使用Base64编码绕过某些过滤 ?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8第一行直接注入phpinfo()代码。第二行做了同样的事情但代码经过了Base64编码。data://协议会识别;base64,标识自动解码后面的内容。这在绕过对?php等标签的简单过滤时非常有效。zip:// 与 phar://压缩包内的攻击这两个协议常用于文件上传包含的组合攻击场景。假设你能上传一个ZIP或PHAR压缩包并且服务器存在文件包含点且你知道压缩包在服务器上的存储路径你就可以直接包含压缩包内的文件。// 假设上传了shell.zip内含shell.php保存在/tmp/shell.zip ?filezip:///tmp/shell.zip%23shell.php // 注意#号需要URL编码为%23phar://协议功能更强大它不仅能包含文件其元数据metadata反序列化特性还能触发其他漏洞。这在CTF中属于进阶考点。理解这些协议是第一步更重要的是理解它们在不同过滤场景下的变形与组合。接下来我们就进入实战看看如何将这些知识应用于解题。3. 实战案例深度拆解一道Warmup题的完整攻防我们虚构一道典型的Warmup题目它融合了多个常见的考点。题目源码index.php简化后如下?php highlight_file(__FILE__); $file $_GET[file]; if(isset($file)){ if (stristr($file, flag) ! false) { die(Hacker!); } if (stristr($file, php://) ! false || stristr($file, data://) ! false) { die(Filtered!); } include($file); } else { show_source(__FILE__); } ?3.1 代码审计与思路分析首先我们进行代码审计这是解题的“地图”。漏洞点第8行include($file);用户通过GET参数file完全控制包含的文件路径。第一层过滤第5-6行使用stristr检查$file中是否包含字符串flag不区分大小写。如果包含直接终止脚本并输出Hacker!。这阻止了我们直接包含flag.php或路径中含flag的文件。第二层过滤第7-8行同样使用stristr检查$file是否包含php://或data://。如果包含终止并输出Filtered!。这直接封堵了最常用的php://filter和data://协议。题目意图很明显它告诉你漏洞在哪但给你设置了两道障碍。我们的任务就是寻找绕过方法。思路链如下目标肯定是读取flag.php- 直接包含被flag关键词拦截 - 尝试用伪协议读取被php://和data://拦截 - 需要找到过滤的弱点或使用其他协议。3.2 绕过策略与实操Payload构造绕过“flag”关键词过滤stristr是字符串匹配函数我们的Payload中不能出现flag这个子串。但我们要读的文件名很可能就是flag.php。怎么办路径遍历也许flag不在当前目录或者文件名不叫flag.php可以尝试包含常见路径或进行目录爆破但Warmup题通常不会这么复杂。编码绕过stristr检查的是原始字符串不会自动解码。但PHP在包含文件时会对路径进行一定的处理。我们可以尝试使用URL编码或双重URL编码。flag的URL编码是%66%6c%61%67。尝试?filephp://filter/.../resource%66%6c%61%67.php。但注意第二层过滤也会检查php://。利用PHP伪协议的特性这是本题的关键。php://filter在读取文件时resource参数指定的文件名是否也会被stristr检查呢答案是会。因为stristr检查的是整个$file参数字符串。所以?filephp://filter/.../resourceflag.php会因为包含flag而被第一层过滤拦截。那么突破口在哪在于第二层过滤只检查了php://和data://但没有检查其他写法。绕过“php://”过滤stristr是大小写敏感的吗不stristr中的i就代表case-insensitive即不区分大小写。所以PHP://、PhP://都会被匹配到此路不通。 但是PHP在处理协议时协议名本身是大小写不敏感的吗是的这是一个关键点。php://filter和PHP://FILTER对PHP引擎来说是等价的。然而过滤函数已经堵死了所有大小写变种。我们需要换个思路有没有其他伪协议或者有没有办法让php://这个字符串“消失”或“变形”终极绕过利用php://filter的编码过滤器链php://filter的强大之处在于可以串联多个过滤器。其中有一类过滤器叫convert.iconv.*用于字符集转换。我们可以利用它进行一个“偷梁换柱”的操作在传输过程中改变Payload的样貌使其绕过过滤在被包含时又恢复原状。构造Payload如下?filephp://filter/convert.iconv.UTF-8.UTF-16/resourceflag.php或者更复杂一些?filephp://filter/convert.iconv.UTF-8.UTF-16|convert.iconv.UTF-16.UTF-8/resourceflag.php为什么这样能绕过绕过“flag”过滤注意我们的resource参数写的仍然是flag.php。如果直接提交肯定被第一层过滤拦截。但是convert.iconv过滤器会改变整个数据流包括resourceflag.php这部分“元数据”吗不它主要改变的是resource指向的文件内容。对于绕过stristr($file, flag)的帮助可能有限除非过滤逻辑有误。实际上更常见的做法是利用编码将flag.php本身的内容进行转换输出。我们真正的目标不是让路径绕过检查而是让包含flag.php这个动作能发生。既然路径中有flag会被拦那我们能不能包含一个“中性”的文件然后通过过滤器“间接”读到flag.php的内容这通常需要配合其他漏洞如日志注入、会话文件包含等。在本题的简单模型中我们假设第一层过滤存在逻辑缺陷例如只在某些情况下触发或者flag文件的名字并非flag.php如fl4g.php我们通过目录遍历发现它后用此Payload读取。这是一种综合考察。更常见的场景是绕过php://过滤有些粗糙的过滤函数如str_replace可能只替换一次php://。我们可以使用phphp://经过一次替换后变成php://。但本题用的是stristr是检查是否存在此方法无效。因此对于本题的严谨解法我们需要重新审视题目。一个更合理的Warmup考点是过滤了php://和data://但没过滤zip://或phar://同时题目存在文件上传点或已知服务器上存在一个可控的zip文件。或者过滤函数存在缺陷例如if (str_replace(php://, , $file) ! $file) { die(Filtered!); }这时我们可以用?filephphp://input来绕过因为str_replace执行后字符串中的php://被移除但剩下的ph和input组合不成php://检查通过而最终包含时路径是phphp://inputPHP能正确识别吗实际上phphp://不是合法协议会失败。正确的绕过是?filephp://filter/convert.base64-encode/resourceflag.php但php://被替换为空变成了filter/convert...这也不是合法路径。所以这种过滤本身就有问题但现实中确实存在。假设本题过滤存在缺陷我们最终构造的读取flag的Payload可能是?filePHP://filter/convert.base64-encode/resource./fl4g.php假设我们通过信息收集或目录遍历得知flag文件名为fl4g.php且PHP://的大小写混淆成功绕过了过滤函数因为有些自定义过滤函数可能只用strpos检查小写php://。实操心得在真实解题中第一步一定是测试过滤规则。通过提交?filetest、?file../、?filephp://等观察回显信息是Hacker!还是Filtered!或是文件不存在的警告来精确摸清过滤的边界和逻辑。这比盲目猜测要高效得多。4. 漏洞利用的扩展场景与高级技巧文件包含漏洞的威力远不止于读取一个flag.php。一旦与服务器其他特性或漏洞结合就会形成强大的攻击链。4.1 日志文件包含Log Poisoning这是LFI实现代码执行最经典的手段之一。Web服务器如Apache、Nginx和PHP自身都会生成日志记录访问请求、错误信息等。这些日志文件通常有固定的位置且内容部分可控如User-Agent、Referer、请求路径。原理将PHP代码写入日志文件然后通过LFI漏洞包含该日志文件从而执行代码。步骤定位日志路径常见路径如/var/log/apache2/access.log、/var/log/nginx/access.log、/proc/self/fd/xx指向当前进程的文件描述符可能包含stderr。投毒日志发送一个请求在User-Agent或Referer等字段中携带PHP代码例如User-Agent: ?php system($_GET[c]);?。包含日志使用LFI漏洞包含这个日志文件?file/var/log/apache2/access.logcls。注意事项日志文件通常很大包含可能导致超时或内存耗尽。需要精准定位到包含恶意代码的那一行附近有时需要多次尝试或利用tail命令的思路但需要通过LFI执行命令才行这就成了先有鸡还是先有蛋的问题。通常先包含日志确认路径和格式再投毒。4.2 会话文件包含Session PoisoningPHP的会话Session机制会将$_SESSION数组序列化后存储到服务器的一个临时文件中如/tmp/sess_[sessionid]。如果我们可以控制$_SESSION中的部分数据并且知道或能猜测会话文件的路径就可以实现类似日志包含的效果。原理在用户可控的Session变量中注入PHP代码然后通过LFI包含该会话文件。条件存在LFI漏洞。服务器使用文件存储Session默认。我们知道Session ID通常通过Cookie中的PHPSESSID传递从而能推算出文件路径/tmp/sess_[PHPSESSID]。有办法向$_SESSION中写入可控数据例如存在一个“设置昵称”的功能将昵称存入$_SESSION[nickname]。利用将昵称设置为?php system($_GET[cmd]);?然后包含文件/tmp/sess_[你的PHPSESSID]。4.3 配合文件上传获取Webshell这是非常常见的组合拳。如果网站同时存在文件上传漏洞和文件包含漏洞但上传的文件被重命名、移动到了非Web目录、或后缀被强制改为图片格式导致无法直接访问执行。原理上传一个图片马内容为?php system($_GET[‘c’]);?的图片文件然后通过LFI漏洞去包含这个上传后的文件。因为include()函数并不关心文件后缀只要文件内容包含有效的PHP代码它就会执行。关键你需要知道上传文件在服务器上的完整绝对路径。这可能需要结合目录遍历、报错信息泄露或上传功能本身的回显来获取。4.4 利用环境变量与特殊文件Linux系统中有一些特殊的文件其内容可以被用户间接影响从而用于包含攻击。/proc/self/environ包含了当前进程的环境变量。其中HTTP_USER_AGENT、HTTP_REFERER等可由HTTP请求控制。攻击方式与日志包含类似。/proc/self/fd/[数字]文件描述符。有时Web服务器的错误日志会重定向到某个文件描述符包含它可能读到错误日志进而实施日志投毒。php://temp或php://memory这些是可读写的临时流。如果脚本先向其中写入了数据可能通过其他参数可控再包含它也可能造成代码执行。但这需要非常特殊的代码逻辑。5. 防御策略与安全编程实践理解了攻击才能更好地防御。作为开发者应如何避免文件包含漏洞5.1 白名单机制最有效的防御绝对不要信任用户输入。如果必须根据输入动态包含文件请使用白名单。$allowed_pages [home.php, about.php, contact.php]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include(./templates/ . $page); } else { include(./templates/404.php); }将允许包含的文件名限定在一个明确的、预定义的列表中。5.2 固定目录与后缀剥离如果做不到白名单也要严格限制文件路径。固定目录将所有可包含的文件放在一个特定目录下并在包含前拼接固定路径。$file ./includes/ . basename($_GET[module] . .php); include($file);basename()函数可以防止目录遍历如../../../etc/passwd会被处理为passwd。强制后缀自动添加后缀避免用户控制后缀名。$file $_GET[file] . .php; // 用户传入data://text/plain,?php...会变成data://text/plain,?php....php无法被正确解析。 include(./pages/ . $file);但要注意如果用户传入file../../../etc/passwd%00在旧版本PHP5.3.4中%00空字节会截断后面的.php依然危险。因此要确保PHP版本安全并结合其他方法。5.3 禁用危险的PHP配置在php.ini中进行全局安全加固allow_url_fopen Off禁止通过URL打开文件。allow_url_include Off务必关闭这是防止RFI的最重要设置。open_basedir /var/www/html将PHP可操作的文件范围限制在Web目录内可以有效防御目录遍历读取系统文件。disable_functions system,exec,passthru,shell_exec,...禁用危险函数即使攻击者实现了代码执行也无法调用系统命令极大增加攻击难度。5.4 输入验证与过滤对用户输入进行严格的验证和过滤。类型检查确保输入是预期的类型。正则表达式过滤过滤掉../、php://、data://等危险字符串。但要注意绕过技巧如双重编码、大小写、超长字符串截断等。使用安全函数如realpath()函数可以解析路径中的符号链接和../并返回绝对路径然后检查这个绝对路径是否在白名单目录下。5.5 代码审计与安全测试在开发流程中引入安全环节。静态代码分析SAST使用工具自动扫描代码中的危险函数include,require等和用户输入点。动态应用安全测试DAST使用漏洞扫描器对运行中的应用进行测试。人工代码审计对涉及文件操作、包含、加载的代码进行重点审查。文件包含漏洞如同一面镜子映照出“信任”与“安全”的永恒博弈。从一道简单的Warmup题目入手我们不仅看到了一个漏洞点的利用更串联起了代码审计、协议理解、绕过技巧、组合利用和最终防御的完整知识链。在CTF中破解它是为了在真实世界里更好地守护它。每一次成功的绕过都应当转化为一行更严谨的代码。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门