php-filter学习
本来是想单独对php-filter chain的convert.iconv 字符转换研究的,但是突然翻到p神文章和其他文章,故想进一步学习一下。
参考:php://filter过滤器学习记录 | tyskillのBlog
参考:谈一谈php://filter的妙用 | 离别歌 (leavesongs.com)
php://filter
是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
对php://filter
的过滤器的作用的理解:
参数
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符(` |
write=<写链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符(` |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 |
过滤器
1 |
|
字符串过滤器
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值在128255之间的字符串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 字节的二进制数据。根据密码而定 |
php://filter
在XXE中的应用:
php://filter经常出现在XXE漏洞中,因为XXE漏洞的特殊性,我们在读取HTML、PHP等文件时可能会抛出此类错误parser error : StartTag: invalid element name
。因为PHP是基于标签的脚本语言:<?php ?>
,这个语法也与XML相符合,所以在解析XML的时候会被误认为是XML,其中的内容(如特殊字符)又有可能和标准XML冲突,所以导致出错。
所以,为了读取包含有敏感信息的PHP等源文件,我们就要先将”可能引发冲突的PHP代码”编码一遍,用到php://filter。
php://filter是PHP语言中特有的协议流,作用是作为一个”中间流”来处理其他流。比如,我们可以将如下一行代码将POST内容转换成base-64编码并输出:
readfile("php://filter/read=convert.base64-encode/resource=php://input");
所以,在XXE中,我们也可以将PHP等容易引发冲突的文件流用php://filter协议流处理一遍,这样就能有效规避特殊字符造成混乱。
我们可以使用的是php://filter/read=convert.base64-encode/resource=./xxe.php
1 |
|
利用php://filter绕过die或exit
利用base64_decode特性
1 |
|
$content
在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了。
但是这里的$_POST['filename']
是可控制协议的,我们即可使用php://filter流的base64-decode方法,将$content
解码,利用php base_decode函数特性去除”死亡exit”。
在base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。
所以,一个正常的base64_decode实际上可以理解为如下两个步骤:
1 |
|
所以,当$content
被加上了<?php exit; ?>
以后,我们可以使用php://filter/write=convert.base64-decode来首先对其解码。在解码过程中,字符<、?、;、>、空格等一共有7个字符不符合base64编码的字符范围将被忽略,所以最终被解码的字符仅有”phpexit”和我们传入的其他字符。
“phpexit”一共7个字符,因为base64算法解码时是4个byte一组,所有给他增加一个”a”一共8个字符。”phpexita”被正常解码,而后面我们传入的webshell的base64内容也被正常解码。结果就是<?php exit; ?>
没有了
此处我用POST传参没反应,故改为GET
1 |
|
payload为:
1 |
|
结果:
利用字符串操作方法
除了直接使用base64特性的方法以外,我们还可以使用strip_tags
函数去除<?php exit; ?>
,php://filter也是支持这个方法的。
使用php://filter/read=string.strip_tags/resource=php://input
在文件中写入echo readfile('php://filter/read=string.strip_tags/resource=php://input');
可以查看效果:
可以看到<?php exit; ?>
被去除掉了,但是如果我们想写一个webshell同样会被去除。
还好php://filter允许使用多个过滤器,我们可以先将webshell用base64编码。在调用完成strip_tags后再进行base64-decode。”死亡exit”在第一步被去除,webshell在第二步被还原。
利用rot13编码
在不开起short_open_tag的情况下,可以实现
对webshell进行rot13编码ROT13加密/解密
写入webshell
查看shell111.php
注意:只有在短标签未开起情况下<?cuc rkvg; ?>
才不会被识别为php。
利用convert.iconv.*
<?php exit();
字符一共13位
1 |
|
写入
查看
filter chain构造与convert.iconv 字符转换
通过iconv -l
可以查看本地的iconv编码:
这里我们以原理核心 convert.iconv
的CSISO2022KR
为例子
特性一(去除不可见字符)
1 |
|
运行结果:
经过从UTF-8->CSISO2022KR
后,bbb变成七个字节,但是只显示了三个b,因为剩下还有几个不可见字符,我们把下面的十六进制转换为文本16进制转换
可以看到输出的是$)Cbbb
。
但是由于base-64的特性,将这串字符base-64解码后,$)
作为无效字符将会被删掉,解码得到的’ ��’就是由Cbbb解码得到的,证明$)
确实被删除。
我们再对解码后的字符串用base-64加密后则会得到Cbbb
利用特性一,我们可以构造一个字母C并且拼接到bbb之前。
特性二(连续可连接性)
1 |
|
输出结果:
在$content1
中构造出了字符1
,$content2
是我们特性一
中构造的字符C
,第三条输出$content3
结果是在第二条结果的过滤器基础上加上了第一条的过滤器,得出的结果就是构造出的"1"
在第一位,然后其他的往后推移了一位,这样就把构造出的1
和C
连接在了一起,如果我们能构造26个字母和所有数字,就可以通过filterchain构造任意语句。
但是如果想要一个一个构造,效率太低,这里有一个php filter chain的脚本可以高效构造字符串。
filter_chain_generator (github.com)
构造语法python3 php_filter_chain_generator.py --chain '<?php phpinfo(); ?> '
其中,php://temp
流会创建一个临时文件,用来存写数据。具体获得这个文件名用sys_get_temp_dir()
函数,它默认的内容为空
打开py脚本可以看到每个字符对应的编码
生成php filter chain的函数