php支持的协议和封装的协议:

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

CTF中主要出现的是php://,zlib,data,phar,所以文章主要研究的是这四种,其他的只是简单的学习一下。

file://、http://&https、ftp://&ftps://、glob://

file://

文件系统 是 PHP 使用的默认封装协议,展现了本地文件系统。
1.php代码

1
2
3
4
<?php
$hello = 'hello,world';
echo $hello;
?>

2.php代码

1
2
3
4
<?php
include($_REQUEST['s']);
var_dump(file_get_contents($_REQUEST['s']));
?>

访问

1
http://127.0.0.1/phpagree/2.php?s=file:///绝对路径\1.php

此时回显的是hello,world,这里有个小问题,1.php中的echo执行了,但是2.php中却是空的,这是因为已经被解析了,查看源代码即可看到。

http://&https://

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

1
2
3
4
<?php
$hello = 'hello,world';
echo $hello;
?>

2.php代码

1
2
3
<?php
var_dump(file_get_contents($_REQUEST['s']));
?>

访问

1
http://127.0.0.1/phpagree/2.php?s=http://127.0.0.1/phpagree/1.php

此时回显的是hello,world,并且就是http://127.0.0.1/phpagree/1.php的内容

ftp://&ftps://

允许通过 FTP 读取存在的文件,以及创建新文件。 如果服务器不支持被动(passive)模式的 FTP,连接会失败。
由于暂时没有搭建ftp的服务器,所以暂时无法演示。

glob://

查找匹配的文件路径模式
1.php代码

1
2
3
4
<?php
$hello = 'hello,world';
echo $hello;
?>

2.php代码

1
2
3
4
5
6
7
8
9
10
<?php
// 循环 ext/spl/examples/ 目录里所有 *.php 文件
// 并打印文件名和文件尺寸
$it = new DirectoryIterator($_REQUEST['s']);

foreach($it as $f) {

printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>

结果如下

php://、data://、zlib://、phar://

php://

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

首先要确保,在php.ini中allow_url_include设置为On,因为allow_url_include依赖于allow_url_fopen,所以allow_url_fopen也需要开启。

php://input

访问请求的原始数据的只读流,可以接收到post的数据,但是当enctype=”multipart/form-data” 的时,php://input无效

1.php代码

1
2
3
4
<?php
$hello = 'hello,world';
echo $hello;
?>

2.php代码

1
2
3
4
<?php
include($_GET['s']);
var_dump(file_get_contents($_REQUEST['s']));
?>

访问

1
http://127.0.0.1/phpagree/2.php?s=php://input

并且POST1.php
此时回显:

可以看到1.php直接被当成了一个字符串,并没有解析。
但是当POST<?php echo 'hello,world'; ?>时,直接就解析了:

因为php://input相当于直接包含了<?php echo 'hello,world'; ?>,并且解析,类似于include。

php://output

是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。

2.php代码

1
2
3
4
5
<?php
$output = fopen($_REQUEST['s'],'w');
fwrite($output, "hello,world");
fclose($output);
?>

访问

1
http://127.0.0.1/phpagree/2.php?s=php://output

此时回显:

php://filter

是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

参数:

1
2
3
4
resource=<要过滤的数据流>	这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

1.php代码

1
2
3
4
<?php
$hello = 'hello,world';
echo $hello;
?>

2.php代码

1
2
3
4
<?php
include($_REQUEST['s']);
//var_dump(file_get_contents($_REQUEST['s']));
?>

此时的回显是:

base64解码之后就是1.php的内容了

再来看一下bugku上题的payload

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

比如这个payloadread=convert.base64-encoderesource=index.php
过滤器还有很多种,这里给出官方文档
CTF中用的最多的还是convert.base64-encode,其他的了解一下就好。
还有一个小问题,为什么include的时候没有解析,因为经过base64编码后,不会被直接解析。

data://

数据(RFC 2397)
用法data://text/plain;base64,

2.php代码

1
2
3
4
<?php
include($_REQUEST['s']);
//var_dump(file_get_contents($_REQUEST['s']));
?>

访问

1
http://127.0.0.1/phpagree/2.php?s=data://text/plain;base64,PD9waHANCgkkaGVsbG8gPSAnaGVsbG8sd29ybGQnOw0KCWVjaG8gJGhlbGxvOw0KPz4=

即可得到hello,world,因为PD9waHANCgkkaGVsbG8gPSAnaGVsbG8sd29ybGQnOw0KCWVjaG8gJGhlbGxvOw0KPz4=base64解码之后就是1.php的内容,include包含之后就解析成功了。

来看hackinglab一道题的payload:

1
URL?^.^=data://text/plain;charset=unicode,(●'◡'●)

wp不仔细说,用法data:[<MIME-type>][;charset=<encoding>][;base64],<data>

zlib://&phar://

zlib://

压缩流
用法zip://archive.zip#dir/file.txt

phar://

PHP 归档
用法类似于zlib://,这两种主要是用于getshell,本地不方便做实验,所以就不用演示了。