PHP 伪协议总结
前一篇对 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://input 、 php://stdin 、 php://memory 和 php://temp `需要开启`allow_url_include`。
php://
访问各个输入/输出流 I/O streams)
,在 CTF 中经常使用的是 php://filter
和 php://input
, php://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-encode
和 convert.base64-decode
为例,使用这两个过滤器等同于分别用 base64_encode()
和 base64_decode()
函数处理所有的流数据。
convert.base64-encode
支持以一个关联数组给出的参数。如果给出了 line-length
, base64
输出将被用 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-encode
和 convert.quoted-printable-decode
两个过滤器。
使用此过滤器的 decode
等同于用 quoted_printable_decode()
函数处理所有的流数据。没有和 convert.quoted-printable-encode
对应的函数。
convert.quoted-printable-encode
支持以一个关联数组给出的参数。除了支持和 convert.base64-encode
一样的附加参数外, convert.quoted-printable-encode
还支持布尔参数 binary
和 force-encode-first
。 convert.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
的前提下。也可以通过安装来自 PECL
的 zlib_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.compress
和 bzip2.decompress
工作的方式与上面讲的 zlib.*
过滤器相同。 自 PHP 5.1.0
起可用,在激活 bz2
支持的前提下。也可以通过安装来自 PECL
的 bz2_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 一个只写的数据流, 允许以
print
和echo
一样的方式 写入到输出缓冲区。 - php://fd 允许直接访问指定的文件描述符。 例如
php://fd/3
引用了文件描述符3
。 - php://memory/php ://temp 一个类似文件包装器的数据流,允许读写临时数据。
- php://stdin/php ://stdout/ php://stderr 允许直接访问 PHP 进程相应的输入或者输出流
http(s):// 协议
用以访问 HTTP(s)
网址,允许通过 HTTP 1.0
的 GET
方法,以只读访问文件或资源。 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.txt
为phpinfo.zip
,压缩包重命名为phpinfo.jpg
,并上传
http://127.0.0.1/include.php?file=zip://home/test/WWW/phpinfo.jpg%23phpinfo.txt
- 示例-使用
compress.bzip2://file.bz2
压缩phpinfo.txt
为phpinfo.bz2
并上传(同样支持任意后缀名)
http://127.0.0.1/include.php?file=compress.bzip2://home/test/WWW/phpinfo.bz2
- 示例-使用
compress. zlib://file.gz
压缩phpinfo.txt
为phpinfo.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_fopen
与 allow_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.*://
封装协议,必须安装来自 PECL
的 SSH2
扩展,主要形式有:
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
提供了对进程 stdio
、 stdout
和 stderr
的访问,默认未开启,使用须安装 PECL
上的 Expect
扩展。用法如下:
expect://command
参考资料
PHP 支持和封装的协议
php:// 官方文档
PHP 可用过滤器列表
扩展阅读
php 伪协议实现命令执行的七种姿势
PHP 文件包含漏洞利用思路与 Bypass 总结
利用 phar 拓展 php 反序列化漏洞攻击面
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: xctf_oj_web_wtf.sh
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论