PHP 伪协议总结

发布于 2024-09-21 08:14:53 字数 23123 浏览 15 评论 0

前一篇对 SSRF 的总结,实际上里面也是涉及到一些协议的,这里也在自己之前学习 PHP 伪协议时的笔记基础上进行扩充,对 PHP 伪协议进行一下总结。

PHP 支持的伪协议

php 伪协议,事实上是其支持的协议与封装协议。而其支持的协议有:

php:// — 访问各个输入/输出流(I/O streams)
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

php:// 协议

使用条件:

不需要开启`allow_url_fopen`
仅` php://inputphp://stdinphp://memoryphp://temp `需要开启`allow_url_include`。

php:// 访问各个输入/输出流 I/O streams) ,在 CTF 中经常使用的是 php://filterphp://inputphp://filter 用于读取源码, php://input 用于执行 php 代码。

下面对其进行详细介绍:

php://filter

CTF 中常用的伪协议,可以用来读取文件,是一种元封装器,设计用于数据流打开时的筛选过滤应用。其中一种最典型的利用方式如下:

index.php?file=php://filter/read=convert.base64-encode/resource=index.php    

// 这里读的过滤器为 convert.base64-encode,就和字面上的意思一样,把输入流 base64-encode。
// resource=upload.php,代表读取 upload.php 的内容

上述代码意为使用 base64 编码的形式将 index.php 读取出来。对于上述利用方式具体每部分的含义,可以参照下图

在这里,涉及到一个过滤器的概念, PHP 过滤器用于验证和过滤来自非安全来源的数据,比如用户的输入。在这里之所以可以使用过滤器读取文件,相当于是将文件作为过滤器输入,获取其经过处理之后的数据流,而在 PHP 中,过滤器有很多种,分别为:

字符串过滤器

string.rot13
// 进行 rot13 转换
// 自 PHP 4.3.0 起,使用此过滤器等同于用 str_rot13() 函数处理所有的流数据。

string.toupper
// 将字符全部大写

string.tolower
// 将字符全部小写

string.strip_tags
// 去除空字符、HTML 和 PHP 标记后的结果。
// 功能类似于 strip_tags() 函数,若不想某些字符不被消除,后面跟上字符,可利用字符串或是数组两种方式。
  • 示例-string.rot13
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'string.rot13');
fwrite($fp, "This is a test.\n");
?>

/* Outputs: Guvf vf n grfg. */
// string.toupper(自 PHP 5.0.0 起)使用此过滤器等同于用 strtoupper() 函数处理所有的流数据。
  • 示例-string.toupper
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'string.toupper');
fwrite($fp, "This is a test.\n");
?>

/* Outputs: THIS IS A TEST. */
// string.tolower(自 PHP 5.0.0 起)使用此过滤器等同于用 strtolower() 函数处理所有的流数据。
  • 示例-string.tolower
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'string.tolower');
fwrite($fp, "This is a test.\n");
?>

/* Outputs: this is a test. */

转换过滤器

如同 string.* 过滤器, convert.* 过滤器的作用就和其名字一样。转换过滤器是 PHP 5.0.0 添加的。

以常用的 convert.base64-encodeconvert.base64-decode 为例,使用这两个过滤器等同于分别用 base64_encode()base64_decode() 函数处理所有的流数据。

convert.base64-encode 支持以一个关联数组给出的参数。如果给出了 line-lengthbase64 输出将被用 line-length 个字符为 长度而截成块。如果给出了 line-break-chars ,每块将被用给出的字符隔开。这些参数的效果和用 base64_encode() 再加上 chunk_split() 相同。

  • 示例-convert.base64-encode & convert.base64-decode
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-encode');
fwrite($fp, "This is a test.\n");
fclose($fp);

/* Outputs: VGhpcyBpcyBhIHRlc3QuCg== */


$param = array('line-length' => 8, 'line-break-chars' => "\r\n");
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-encode', STREAM_FILTER_WRITE, $param);
fwrite($fp, "This is a test.\n");
fclose($fp);
/* Outputs: VGhpcyBp
: cyBhIHRl
: c3QuCg== */


$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.base64-decode');
fwrite($fp, "VGhpcyBpcyBhIHRlc3QuCg==");
fclose($fp);

/* Outputs: This is a test. */
?>

除此之外,还有 convert.quoted-printable-encodeconvert.quoted-printable-decode 两个过滤器。

使用此过滤器的 decode 等同于用 quoted_printable_decode() 函数处理所有的流数据。没有和 convert.quoted-printable-encode 对应的函数。

convert.quoted-printable-encode 支持以一个关联数组给出的参数。除了支持和 convert.base64-encode 一样的附加参数外, convert.quoted-printable-encode 还支持布尔参数 binaryforce-encode-firstconvert.base64-decode 只支持 line-break-chars 参数作为从编码载荷中剥离的类型提示。

  • 示例-convert.quoted-printable-encode & convert.quoted-printable-decode
<?php
$fp = fopen('php://output', 'w');
stream_filter_append($fp, 'convert.quoted-printable-encode');
fwrite($fp, "This is a test.\n");

/* Outputs: =This is a test.=0A */
?>

压缩过滤器

正如名字所提到的,其作用也是类似,虽然在 PHP 伪协议中,有压缩封装协议 (zlib://, bzip2://, zip://) ,提供了在本地文件系统中 创建 gzip 和 bz2 兼容文件的方法,但不代表可以在网络的流中提供通用压缩的意思,也不代表可以将一个非压缩的流转换成一个压缩流。对此,压缩过滤器可以在任何时候应用于任何流资源。

另外,需要注意的是,压缩过滤器不产生命令行工具如 gzip 的头和尾信息。只是压缩和解压数据流中的有效载荷部分。其中主要有 zlib.* , bzip2.* 两类压缩过滤器:

zlib.deflate (压缩)和 zlib.inflate (解压)实现了 RFC 1951 中的的压缩算法。 zlib.* 压缩过滤器自 PHP 版本 5.1.0 起可用,在激活 zlib 的前提下。也可以通过安装来自 PECLzlib_filter 包作为一个后门在 5.0.x 版中使用。此过滤器在 PHP 4 中不可用。

其中,deflate 过滤器最多可以接受三个参数。分别为:

  • level 定义了压缩强度(1-9)。数字更高通常会产生更小的载荷,但要消耗更多的处理时间。存在两个特殊压缩等级:0(完全不压缩)和 -1(zlib 内部默认值,目前是 6)。
  • window 压缩回溯窗口大小,以二的次方表示。更高的值(大到 15 —— 32768 字节)产生更好的压缩效果但消耗更多内存,低的值(低到 9 —— 512 字节)产生产生较差的压缩效果但内存消耗低。目前默认的 window 大小是 15。
  • memory 用来指示要分配多少工作内存。合法的数值范围是从 1(最小分配)到 9(最大分配)。内存分配仅影响速度,不会影响生成的载荷的大小。

下面为该压缩过滤器的示例:

  • 示例-zlib.deflate 和 zlib.inflate
<?php
$params = array('level' => 6, 'window' => 15, 'memory' => 9);

$original_text = "This is a test.\nThis is only a test.\nThis is not an important string.\n";
echo "The original text is " . strlen($original_text) . " characters long.\n";

$fp = fopen('test.deflated', 'w');
stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
fwrite($fp, $original_text);
fclose($fp);

echo "The compressed file is " . filesize('test.deflated') . " bytes long.\n";
echo "The original text was:\n";

/* Use readfile and zlib.inflate to decompress on the fly */
readfile('php://filter/zlib.inflate/resource=test.deflated');

/* Generates output:

The original text is 70 characters long.
The compressed file is 56 bytes long.
The original text was:
This is a test.
This is only a test.
This is not an important string.

*/
?>
  • 示例-zlib.deflate 简单参数用法
<?php
$original_text = "This is a test.\nThis is only a test.\nThis is not an important string.\n";
echo "The original text is " . strlen($original_text) . " characters long.\n";

$fp = fopen('test.deflated', 'w');
/* Here "6" indicates compression level 6 */
stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, 6);
fwrite($fp, $original_text);
fclose($fp);

echo "The compressed file is " . filesize('test.deflated') . " bytes long.\n";

/* Generates output:

The original text is 70 characters long.
The compressed file is 56 bytes long.

*/
?>

bzip2.compressbzip2.decompress 工作的方式与上面讲的 zlib.* 过滤器相同。 自 PHP 5.1.0 起可用,在激活 bz2 支持的前提下。也可以通过安装来自 PECLbz2_filter 包作为一个后门在 5.0.x 版中使用。此过滤器在 PHP 4 中 不可用。

bzip2.compress 过滤器接受最多两个参数:

  • blocks 从 1 到 9 的整数值,指定分配多少个 100K 字节的内存块作为工作区。
  • work 0 到 250 的整数值,指定在退回到一个慢一些,但更可靠的算法之前做多少次常规压缩算法的尝试。调整此参数仅影响到速度,压缩输出和内存使用都不受此设置的影响。将此参数设为 0 指示 bzip 库使用内部默认算法。

bzip2.decompress 过滤器仅接受一个参数,可以用普通的布尔值传递,或者用一个关联数组中的 small 单元传递。当 small 设为 &true ; 值时,指示 bzip 库用最小的内存占用来执行解压缩,代价是速度会慢一些。

下面为其使用示例:

  • 示例-bzip2.compress 和 bzip2.decompress
<?php
$param = array('blocks' => 9, 'work' => 0);

echo "The original file is " . filesize('LICENSE') . " bytes long.\n";

$fp = fopen('LICENSE.compressed', 'w');
stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param);
fwrite($fp, file_get_contents('LICENSE'));
fclose($fp);

echo "The compressed file is " . filesize('LICENSE.compressed') . " bytes long.\n";

/* Generates output:

The original text is 3288 characters long.
The compressed file is 1488 bytes long.

*/
?>

加密过滤器

加密过滤器为 mcrypt.*mdecrypt.* ,使用 libmcrypt 提供了对称的加密和解密。这两组过滤器都支持 mcrypt 扩展库中相同的算法,格式为 mcrypt.ciphername ,其中 ciphername 是密码的名字,将被传递给 mcrypt_module_open() 。有以下五个过滤器参数可用:

  • 示例-用 3DES 将文件加密输出
<?php
$passphrase = 'My secret';

/* Turn a human readable passphrase
* into a reproducable iv/key pair
*/
$iv = substr(md5('iv'.$passphrase, true), 0, 8);
$key = substr(md5('pass1'.$passphrase, true) .
md5('pass2'.$passphrase, true), 0, 24);
$opts = array('iv'=>$iv, 'key'=>$key);

$fp = fopen('secert-file.enc', 'wb');
stream_filter_append($fp, 'mcrypt.tripledes', STREAM_FILTER_WRITE, $opts);
fwrite($fp, 'Secret secret secret data');
fclose($fp);
?>
  • 示例-读取加密的文件
<?php
$passphrase = 'My secret';

/* Turn a human readable passphrase
* into a reproducable iv/key pair
*/
$iv = substr(md5('iv'.$passphrase, true), 0, 8);
$key = substr(md5('pass1'.$passphrase, true) .
md5('pass2'.$passphrase, true), 0, 24);
$opts = array('iv'=>$iv, 'key'=>$key);

$fp = fopen('secert-file.enc', 'rb');
stream_filter_append($fp, 'mdecrypt.tripledes', STREAM_FILTER_WRITE, $opts);
$data = rtrim(stream_get_contents($fp));
fclose($fp);

echo $data;
?>

php://input

php://input 是个可以访问请求的原始数据的只读流,将 post 请求中的数据作为 PHP 代码执行。当传进去的参数作为文件名变量去打开文件时,可以将参数 php://input ,同时 post 方式传进去值作为文件内容,供 php 代码执行时当做文件内容读取

利用条件:

allow_url_include = On
allow_url_fopen = On/Off

利用姿势:

index.php?file=php://input    

POST:
<?php phpinfo();?>/<? phpinfo();?>

需要注意的是,在 PHP 5.6 之前 php://input 打开的数据流只能读取一次; 数据流不支持 seek 操作。 不过,依赖于 SAPI 的实现,请求体数据被保存的时候, 它可以打开另一个 php://input 数据流并重新读取。 通常情况下,这种情况只是针对 POST 请求,而不是其他请求方式,比如 PUT 或者 PROPFIND

php:// 伪协议中,除了上述两种在 CTF 中常用的之外,还有一些其他的,比如下面这些:

  • php://output 一个只写的数据流, 允许以 printecho 一样的方式 写入到输出缓冲区。
  • php://fd 允许直接访问指定的文件描述符。 例如 php://fd/3 引用了文件描述符 3
  • php://memory/php ://temp 一个类似文件包装器的数据流,允许读写临时数据。
  • php://stdin/php ://stdout/ php://stderr 允许直接访问 PHP 进程相应的输入或者输出流

http(s):// 协议

用以访问 HTTP(s) 网址,允许通过 HTTP 1.0GET 方法,以只读访问文件或资源。 HTTP 请求会附带一个 Host: 头,用于兼容基于域名的虚拟主机。 如果在 php.ini 文件中或字节流上下文 (context) 配置了 user_agent 字符串,它也会被包含在请求之中。使用需要满足以下条件:

allow_url_fopen:on
allow_url_include :on

用法:

http://example.com    
http://example.com/file.php?var1=val1&var2=val2
http://user:password@example.com
https://example.com
https://example.com/file.php?var1=val1&var2=val2
https://user:password@example.com
  • 示例-检测重定向后最终的 URL
<?php
$url = 'http://www.example.com/redirecting_page.php';

$fp = fopen($url, 'r');

$meta_data = stream_get_meta_data($fp);
foreach ($meta_data['wrapper_data'] as $response) {

/* 我们是否被重定向了? */
if (strtolower(substr($response, 0, 10)) == 'location: ') {

/* 更新我们被重定向后的 $url */
$url = substr($response, 10);
}
}
?>

ftp(s):// 协议

用以访问 FTP(s) URLs ,允许通过 FTP 读取存在的文件,以及创建新文件。 如果服务器不支持被动 (passive) 模式的 FTP ,连接会失败。

打开文件后你既可以读也可以写,但是不能同时进行。 当远程文件已经存在于 ftp 服务器上,如果尝试打开并写入文件的时候, 未指定上下文 (context) 选项 overwrite ,连接会失败。 如果要通过 FTP 覆盖存在的文件, 指定上下文 (context)overwrite 选项来打开、写入。 另外可使用 FTP 扩展来代替。

需要注意的是:如果设置了 php.ini 中的 from 指令,这个值会作为匿名 (anonymous)ftp 的密码。

zlib:// & bzip2:// & zip:/ 协议

php 伪协议中的压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀 (jpg png gif xxx) 等等。其使用条件为:

allow_url_fopen:off/on
allow_url_include :off/on
  • 示例-使用 zip:// 压缩 phpinfo.txtphpinfo.zip ,压缩包重命名为 phpinfo.jpg ,并上传
http://127.0.0.1/include.php?file=zip://home/test/WWW/phpinfo.jpg%23phpinfo.txt    
  • 示例-使用 compress.bzip2://file.bz2 压缩 phpinfo.txtphpinfo.bz2 并上传(同样支持任意后缀名)
http://127.0.0.1/include.php?file=compress.bzip2://home/test/WWW/phpinfo.bz2    
  • 示例-使用 compress. zlib://file.gz 压缩 phpinfo.txtphpinfo.gz 并上传(同样支持任意后缀名)
http://127.0.0.1/include.php?file=compress.zlib://home/test/WWW/phpinfo.gz    

data:// 协议

PHP>=5.2.0 起,可以使用 data:// 数据流封装器,以传递相应格式的数据。通常可以用来执行 PHP 代码。使用条件为:

allow_url_fopen:on
allow_url_include :on

可以支持明文或编码,用法为:

data://text/plain    ,XXX
data://text/plain ;base64,XXX

格式为:
data://资源类型;编码,内容
  • 示例-使用明文方式读取 phpinfo()
http://127.0.0.1/include.php?file=data://text/plain,    <?php%20phpinfo();?>

  • 示例-使用 base64 方式(CTF 中可用以绕过 waf)读取 phpinfo()
http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b    
  • 示例-打印 data:// 的内容
<?php
// 打印 "I love PHP"
echo file_get_contents('data://text/plain ;base64,SSBsb3ZlIFBIUAo=');
?>

glob:// 协议

查找匹配的文件路径模式, 自 PHP 5.3.0 起开始有效。下面为其基本用法:

<?php
// 循环 ext/spl/examples/ 目录里所有 *.php 文件
// 并打印文件名和文件尺寸
$it = new DirectoryIterator("glob://ext/spl/examples/ *.php");
foreach($it as $f) {
printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>

output:

tree.php: 1.0K
findregex.php: 0.6K
findfile.php: 0.7K
dba_dump.php: 0.9K
nocvsdir.php: 1.1K
phar_from_dir.php: 1.0K
ini_groups.php: 0.9K
directorytree.php: 0.9K
dba_array.php: 1.1K
class_tree.php: 1.8K

phar:// 协议

用以 PHP 归档 ,数据流包装器自 PHP 5.3.0 起开始有效,与 zip:// 类似,同样可以访问 zip 格式压缩包内容,比如下面这个例子:

http://127.0.0.1/include.php?file=phar://home/test/WWW/phpinfo.zip/phpinfo.txt    

该伪协议在 CTF 中比较常见,主要用于反序列化和文件包含,此文中只对其用于文件包含进行介绍,反序列化在后面将会单独拿出来讲。在文件包含中,该协议主要用于支持 zip、phar 格式的文件包含,用法如下:

?file=phar://[压缩包文件相对路径]/[压缩文件内的子文件名]
?file=phar://[压缩包文件绝对路径]/[压缩文件内的子文件名]
  • 示例-配合文件上传漏洞,当仅可以上传 zip 格式时
index.php?file=phar://index.zip/index.txt    

index.php?file=phar://home/test/WWW/FI/index.zip/index.txt
  • 示例-配合文件上传漏洞,当仅可以上传图片格式时, phar:// 不管后缀是什么,都会当做压缩包来解压。
index.php?file=phar://head.png/head.txt    

index.php?file=phar://home/test/WWW/FI/head.png/head.txt

file:// 协议

用于访问本地文件系统,可以使用相对路径或绝对路径来访问文件系统文件,其使用样例为:

/path/to/file.ext
relative/path/to/file.ext
fileInCwd.ext
C:/path/to/winfile.ext
C:\path\to\winfile.ext
\\smbserver\share\path\to\winfile.ext
file:///path/to/file.ext

该伪协议在 CTF 中通常用来读取本地文件,因为其在双 off 的情况下也可以正常使用,不受 allow_url_fopenallow_url_include 的影响。

  • 示例-使用文件的相对路径和文件名
http://127.0.0.1/include.php?file=./phpinfo.txt    
  • 示例-使用文件的绝对路径和文件名
http://127.0.0.1/include.php?file=file://home/test/WWW/phpinfo.txt    

ssh2:// 协议

Secure Shell 2 ,默认没有激活,如果需要使用 ssh2.*:// 封装协议,必须安装来自 PECLSSH2 扩展,主要形式有:

ssh2.shell://
ssh2.exec://
ssh2.tunnel://
ssh2.sftp://
ssh2.scp://

该伪协议除了支持传统的 URI 登录信息, ssh2 封装协议也支持通过 URL 的主机 (host) 部分来复用打开连接,用法如下所示:

ssh2.    shell://user:pass@example.com:22/xterm    
ssh2. exec://user:pass@example.com:22/usr/local/bin/somecmd
ssh2. tunnel://user:pass@example.com:22/192.168.0.1 :14
ssh2. sftp://user:pass@example.com:22/path/to/filename

下面为一个示例,用以从一个活动连接中打开字节流:

<?php
$session = ssh2_connect('example.com', 22);
ssh2_auth_pubkey_file($session, 'username', '/home/username/.ssh/id_rsa.pub',
'/home/username/.ssh/id_rsa', 'secret');
$stream = fopen("ssh2.tunnel://$session/remote.example.com:1234", 'r');
?>

ogg:// 协议

音频流协议,用以读取 OGG/Vorbis 格式的压缩音频编码,并能通过该伪协议写入或追加压缩音频数据,默认未激活,使用需要安装 PECL 中的 OGG/Vorbis 扩展。用法如下:

ogg://soundfile.ogg    
ogg:///path/to/soundfile.ogg
ogg://http :// www.example.com/path/to/soundstream.ogg

expect:// 协议

用以处理交互式的流,由 expect:// 封装协议打开的数据流 PTY 提供了对进程 stdiostdoutstderr 的访问,默认未开启,使用须安装 PECL 上的 Expect 扩展。用法如下:

expect://command    

参考资料

PHP 支持和封装的协议
php:// 官方文档
PHP 可用过滤器列表

扩展阅读

php 伪协议实现命令执行的七种姿势
PHP 文件包含漏洞利用思路与 Bypass 总结
利用 phar 拓展 php 反序列化漏洞攻击面

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

风渺

暂无简介

文章
评论
28 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文