gilacms 1.11.8 代码审计

今日继续找个cms来审一审,两个中危,两个高危,版本v1.11.8,尝试自己构建一波exp

image-20200204132903798

结果gilaCMS在虚拟机里安装之后访问什么都是404,尝试在windows下搭建成功

这几个洞应该都是后台的洞,其实个人觉得后台洞都挺鸡肋的

gilacms v1.11.8 后台sql注入

先看sql注入这个洞,整个cms的代码结构如下

image-20200204135549962

views下面的文件可以通过url直接访问,而且一个php文件对应一个功能,然而在后台并没有看到sql相关的功能,这里看一下sql.php(/src/core/views/sql.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
<ul class="g-nav">
<li><a href="admin/sql?query=SHOW TABLES">Show Tables</a></li>
</ul>
<form action="<?=gila::url('admin/sql')?>" method="POST">
<textarea class="g-input" style="width:100%" name="query"><?=($q??'')?></textarea>
<p><button class="g-btn" type="submit"><?=__('Execute')?></button></p>
</form><br>

<?php
global $db;

if(!isset($q)) $q='SHOW TABLES';
$res = $db->getAssoc($q);
if($res) {
echo '<div style="width:100%;overflow-x:scroll"><table class="g-table">';
foreach($res as $row) {
if(!isset($thead)) {
echo '<tr>';
foreach($row as $key=>$v) echo '<th>'.$key;
$thead=true;
}
echo '<tr>';
if($q=="SHOW TABLES") {
foreach($row as $key=>$v) echo '<td><a href="admin/sql?query=SELECT * FROM `'.$v.'` LIMIT 30;">'.$v.'</a>';
} else{
foreach($row as $key=>$v) echo '<td>'.htmlentities($v);
}
}
echo '</table>';
}

可以看到直接拼接,导致任意sql语句执行,emmmm,这高危洞感觉很迷

image-20200204140227833

修复后的代码如下

image-20200204140553803

直接写死了,很秀

gilacms v1.11.8 后台路径遍历漏洞

同样是在后台的洞,接着看功能,有post功能,在thumbnail处可以选择上传图片或者在服务器上选择文件,而这里的后台路径遍历漏洞就是出在服务器上选择文件这个功能上了

image-20200204141600720

抓包看一下

image-20200204142102793

尝试更改path,数据包如下

1
2
3
POST /gila/admin/media HTTP/1.1

g_response=content&path=src/core/assets/../../../

可以看到成功路径遍历

image-20200204142422443

看一下漏洞代码,在(/src/core/views/media-assets.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$path = router::request('path', $_COOKIE['asset_path'] ?? 'src');
if($path[0]=='.') $path = 'src';
setcookie('asset_path', $path, time()+86400,'/');
$disabled = ($path=='')?'disabled':'';

$files=[];
if($path=='src') {
$scanned = scandir('src/');
foreach($scanned as $i=>$v) if(is_dir('src/'.$v)) {
$package = json_decode(file_get_contents('src/'.$v.'/package.json'));
if(isset($package->assets)) {
foreach($package->assets as $asset) {
$files[] = 'src/'.$v.'/'.$asset;
}
}
}
}

可以看出并没有做什么过滤,直接可以路径穿透

gilacms v1.11.8 本地文件读取

接着看功能,文章保存之后,在删除的地方存在本地文件读取,payload数据包如下

1
POST /gila/cm/delete?t=../../../../../../../../lfi.txt HTTP/1.1

在参数t处存在本地文件读取

image-20200204161454866

可以说这个漏洞是很奇怪的,为什么删除的地方会存在文件读取呢,看一看源码分析一下,代码在/src/core/controllers/cm.php中的第296行

1
2
3
4
5
6
7
8
9
10
11
12
function deleteAction ()
{
header('Content-Type: application/json');
$gtable = new gTable(router::get("t",1), $this->permissions);
if($gtable->can('delete')) {
$gtable->deleteRow($_POST['id']);
$response = '{"id":"'.$_POST['id'].'"}';
} else {
$response = '"error":"User cannot delete"}';
}
echo $response;
}

跟进gTable(/src/core/classes/gTable.php),第17行

1
2
3
4
5
6
7
8
if(isset(gila::$content[$content]))
$path = 'src/'.gila::$content[$content];
else if(file_exists($content))
$path = $content;
else
$path = 'src'.$content;

$this->table = include $path;

可以看到构造函数中直接include $path,并没有对t参数做出有效过滤,这种写法真的很迷惑

gilacms v1.11.8 后台getshell

这个洞也很是神奇,在发表文章之后,查看thumbnail的图片

image-20200204204508214

可以得到这样的url

1
http://127.0.0.1/gila/lzld/thumb?src=assets/gila-logo.png&media_thumb=80

而src处是可以下载远程文件的,文件路径可以直接访问

image-20200204204743266

.htaccess只过滤了php后缀,如果开启了phtml,可以采用phtml绕过

漏洞代码在/src/core/controllers/lzld.php 第29行开始

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
function thumbAction ()
{
$file = $_GET['src'];
$ext = explode('.',$file);
$ext = $ext[count($ext)-1];
$size = (int)$_GET['media_thumb'] ?? 80;
$file = view::thumb($file, 'media_thumb/', $size);

if (file_exists($file)) {
$imageInfo = getimagesize($file);
ob_end_clean();
switch ($imageInfo[2]) {
case IMAGETYPE_JPEG:
header("Content-Type: image/jpeg");
break;
case IMAGETYPE_WEBP:
header("Content-Type: image/webp");
break;
case IMAGETYPE_GIF:
header("Content-Type: image/gif");
break;
case IMAGETYPE_PNG:
header("Content-Type: image/png");
break;
default:
if($ext=='svg') echo file_get_contents($file);
exit;
break;
}

header('Content-Length: ' . filesize($file));
readfile($file);
} else {
http_response_code(404);
}

}

跟进view::thumb(src/core/classes/view.php),看第407行

1
2
3
4
5
6
7
8
9
static function thumb ($src, $prefix='', $max=180)
{
...
if (!file_exists($file)) {
image::make_thumb($src, $file, $max_width, $max_height, $type??null);
}
event::fire('view::thumb',[$src,$file]);
return $file;
}

如果file不存在则调用image::make_thumb(/src/core/classes/image.php),继续跟进

1
2
3
4
5
static function make_thumb ($src,$file,$max_width,$max_height,$img_type=null)
{
$src = self::local_path($src);
...
}

首先进入local_path函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static function local_path($src)
{
if(parse_url($src, PHP_URL_HOST) != null) if(strpos($src,gila::config('base')) !== 0) {
$_src = SITE_PATH.'tmp/'.str_replace(["://",":\\\\","\\","/",":"], "_", $src);
if(!file_exists($_src)) {
$_file = LOG_PATH.'/cannot_copy.json';
$cannot_copy = json_decode(file_get_contents($_file),true);
if(in_array($src,$cannot_copy)) return false;
if(!copy($src, $_src)) {
$cannot_copy[] = $src;
file_put_contents($_file,json_encode($cannot_copy,JSON_PRETTY_PRINT));
return false;
}
}
return $_src;
}
return $src;
}

可以看到第174行调用了file_get_contents,而file_get_contents,是可以下载远程文件的,这里又并没有进行有效过滤,所以可以从远程服务器下载shell

总之,这个cms在后台的过滤基本为零,漏洞还是挺多的。

0%