gxlcms代码审计

0x00 前言

审计出三个漏洞
1.SQL时间盲注漏洞
2.后台GetShell
3.鸡肋本地包含漏洞

0x01 CMS介绍

官网地址:http://www.gxlcms.com/
Gxlcms有声小说系统是一件采集快速建立一个听书站。用PHP写的,最新版要求PHP>5.4。使用了THINKPHP框架。

源码下载地址:http://bbs.gxlcms.com/article/1

0x03 SQL盲注

漏洞文件:Lib\Home\Action\UpdownAction.class.php
漏洞代码:show函数

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
public function show($id,$type,$model='ting'){
$rs = D(ucfirst($model));
if($type){
$cookie = $model.'-updown-'.$id;
if(isset($_COOKIE[$cookie])){
$data['data']=0;
$data['info']="您已操作过,晚点再试!";
$data['status']=0;
$this->ajaxReturn($data);
}
if ('up' == $type){
$rs->where(array('ting_id'=>$id))->setInc($model.'_up');
setcookie($cookie, 'true', time()+intval(C('user_second')));
}elseif( 'down' == $type)
$rs->where(array('ting_id'=>$id))->setInc($model.'_down');
setcookie($cookie, 'true', time()+intval(C('user_second')));
}
}
$array = $rs->field(''.$model.'_up,'.$model.'_down')->find($id);
if (!$array) {
$array[$model.'_up'] = 0;
$array[$model.'_down'] = 0;
}
$arrays['data']=$array[$model.'_up'].':'.$array[$model.'_down'];
$arrays['info']="感谢您的参与,操作成功!";
$arrays['status']=1;
$this->ajaxReturn($arrays);

//echo($array[$model.'_up'].':'.$array[$model.'_down']);
}

这套CMS使用了THINKPHP框架(自带SQL防注入功能),但是还是存在SQL注入。首先来看下这个函数,这个函数有三个变量:id,type,model。$id经过where方法,而最终经过THINKPHP框架只的parseValue,对单引号进行过滤的。$type,只是进行一个if判断,没有进入mysql语句中。最终出现问题的是出现在$model变量上。

field中调用了$model变量,不过没对$model变量进行过滤,可以进行SQL注入,不过只能进行SQL时间盲注。

可以使用SQLMAP进行注入:

1
sqlmap --dbms=mysql --technique=T -u "http://127.0.0.1/index.php?s=updown-show--id-1-type-1-model-1*"

在以前的老版本中,还有类似的SQL注入,不过新版本修复了。
文件:Lib\Lib\Action\Home\HitsAction.class.php
函数:show()
与上面类似。就不贴代码了。

0x04 后台GetShell

漏洞文件:Lib\Admin\Action\AdminAction.class.php
漏洞函数:configsave()

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
public function configsave()
{
$config = $_POST["config"];
$config["site_tongji"] = stripslashes($config["site_tongji"]);
$config["play_collect_content"] = stripslashes($config["play_collect_content"]);
$config["admin_time_edit"] = (bool) $config["admin_time_edit"];
$config["url_tingdata"] = trim($config["url_tingdata"]);
$config["url_newsdata"] = trim($config["url_newsdata"]);
$config["upload_path"] = str_replace(array("..", "//"), "", $config["upload_path"]);
$config["upload_class"] = trim(str_replace(array("php", "asp", "apsx", "txt", "asax", "ascx", "cdx", "cer", "cgi", "jsp", "html", "html", "htm", ",,"), "", strtolower($config["upload_class"])), ",");
$config["upload_thumb"] = (bool) $config["upload_thumb"];
$config["upload_water"] = (bool) $config["upload_water"];
$config["upload_http"] = (bool) $config["upload_http"];
$config["upload_ftp"] = (bool) $config["upload_ftp"];
$config["play_collect"] = (bool) $config["play_collect"];
$config["play_second"] = intval($config["play_second"]);
$config["tmpl_cache_on"] = (bool) $config["tmpl_cache_on"];
$config["html_cache_on"] = (bool) $config["html_cache_on"];
$config["user_gbcm"] = (bool) $config["user_gbcm"];

foreach (explode(chr(13), trim($config["play_server"])) as $v ) {
list($key, $val) = explode("$\$\$", trim($v));
$arrserver[trim($key)] = trim($val);
}

$config["play_server"] = $arrserver;

foreach (explode(chr(13), trim($config["play_collect_content"])) as $v ) {
$arrcollect[] = trim($v);
}

$config["play_collect_content"] = $arrcollect;
$config["html_cache_time"] = $config["html_cache_time"] * 3600;

if (0 < $config["html_cache_index"]) {
$config["html_cache_rules"]["home:index:index"] = array("{:action}", $config["html_cache_index"] * 3600);
}
else {
$config["html_cache_rules"]["home:index:index"] = NULL;
}

if (0 < $config["html_cache_list"]) {
$config["html_cache_rules"]["home:ting:show"] = array("{:controller}_{:action}/{\$_SERVER.REQUEST_URI|md5}", $config["html_cache_list"] * 3600);
$config["html_cache_rules"]["home:news:show"] = array("{:controller}_{:action}/{\$_SERVER.REQUEST_URI|md5}", $config["html_cache_list"] * 3600);
}
else {
$config["html_cache_rules"]["home:ting:show"] = NULL;
$config["html_cache_rules"]["home:news:show"] = NULL;
}

if (0 < $config["html_cache_content"]) {
$config["html_cache_rules"]["home:ting:read"] = array("{:controller}_{:action}/{name|get_small_id_by_name}{id|get_small_id}/{name|gettingidmd}{id|getmd5}", $config["html_cache_content"] * 3600);
$config["html_cache_rules"]["home:news:read"] = array("{:controller}_{:action}/{\$_SERVER.REQUEST_URI|md5}", $config["html_cache_content"] * 3600);
}
else {
$config["html_cache_rules"]["home:ting:read"] = NULL;
$config["html_cache_rules"]["home:news:read"] = NULL;
}

if (0 < $config["html_cache_play"]) {
$config["html_cache_rules"]["home:ting:play"] = array("{:controller}_{:action}/{name|get_small_id_by_name}{id|get_small_id}/{name|gettingid}{id}/{\$_SERVER.REQUEST_URI|md5}", $config["html_cache_play"] * 3600);
}
else {
$config["html_cache_rules"]["home:ting:play"] = NULL;
}

if (0 < $config["html_cache_ajax"]) {
$config["html_cache_rules"]["home:my:show"] = array("{:controller}_{:action}/{\$_SERVER.REQUEST_URI|md5}", $config["html_cache_ajax"] * 3600);
$config["html_cache_rules"]["home:special:read"] = array("{:controller}_{:action}/{\$_SERVER.REQUEST_URI|md5}", $config["html_cache_ajax"] * 3600);
}
else {
$config["html_cache_rules"]["home:my:show"] = NULL;
$config["html_cache_rules"]["home:special:read"] = NULL;
}

if (0 < $config["html_cache_juqing"]) {
$config["html_cache_rules"]["home:story:read"] = array("{:controller}_{:action}/{name|get_small_id_by_name}{id|get_small_id}/{name|gettingid}{id}/{name|gettingidmd}{id|getmd5}{p}", $config["html_cache_juqing"] * 24 * 3600);
}
else {
$config["html_cache_rules"]["home:story:read"] = NULL;
}

if (0 < $config["html_cache_story"]) {
$config["html_cache_rules"]["home:story:show"] = array("{:controller}_{:action}/{dir|gediridmd}{id|getmd5}{p}", $config["html_cache_juqing"] * 3600);
}
else {
$config["html_cache_rules"]["home:story:show"] = NULL;
}

if (0 < $config["html_cache_actorshow"]) {
$config["html_cache_rules"]["home:actor:show"] = array("{:controller}_{:action}/{\$_SERVER.REQUEST_URI|md5}", $config["html_cache_actorshow"] * 3600);
}
else {
$config["html_cache_rules"]["home:actor:show"] = NULL;
}

if (0 < $config["html_cache_actor"]) {
$config["html_cache_rules"]["home:actor:read"] = array("{:controller}_{:action}/{name|get_small_id_by_name}{id|get_small_id}/{name|gettingidmd}{id|getmd5}", $config["html_cache_actor"] * 24 * 3600);
}
else {
$config["html_cache_rules"]["home:actor:read"] = NULL;
}
if (0 == $config["url_html"]) {
@unlink("./index" . C("html_file_suffix"));
}
else {
$config["html_home_suffix"] = $config["html_file_suffix"];
}

$config_old = require "./Runtime/Conf/config.php";
$config_new = array_merge($config_old, $config);
arr2file("./Runtime/Conf/config.php", $config_new);
@unlink("./Runtime/common~runtime.php");
$gxl_play .= "var gxl_root=\"" . $config["site_path"] . "\";";
$gxl_play .= "var gxl_width=" . $config["play_width"] . ";";
$gxl_play .= "var gxl_height=" . $config["play_height"] . ";";
admin_gxl_hot_key(C("site_hot"));
$this->success("恭喜您,配置信息更新成功!");
}

与函数对应的后台功能截图

1.jpg

主要是看这个设置附件上传类型,危害很明显。但这里不能直接添加php后缀,因为有过滤,把”php”, “asp”, “apsx”, “txt”, “asax”, “ascx”, “cdx”, “cer”, “cgi”, “jsp”, “html”, “html”, “htm”等后缀都过滤掉。把这些字符替换为空。这样就很容易绕过了,添加pphphp,就可以绕过。但是,在上传处,还有一次过滤。

漏洞文件:Lib\Admin\Action\UploadAction.class.php
漏洞函数:upload()

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
public function upload()
{

echo "<div style=\"font-size:12px; height:30px; line-height:30px\">";
$uppath = "./" . str_replace(array("..", "//"), "", C("upload_path")) . "/";
$uppath_s = "./" . str_replace(array("..", "//"), "", C("upload_path")) . "-s/";
$sid = trim($_POST["sid"]);
$fileback = (!empty($_POST["fileback"]) ? trim($_POST["fileback"]) : "ting_pic");

if ($sid) {
$uppath .= $sid . "/";
$uppath_s .= $sid . "/";
mkdirss($uppath);
mkdirss($uppath_s);
}

$up = new \Org\Net\UploadFile();
$up->savePath = $uppath;
$up->saveRule = uniqid;
$up->uploadReplace = true;
$upload_class = str_replace(array("php", "asp", "apsx", "txt", "asax", "ascx", "cdx", "cer", "cgi", "jsp", "html", "html", "htm", ",,"), "", strtolower(C("upload_class")));
$upload_classs = trim($upload_class, ",");
$up->allowExts = explode(",", $upload_classs);
$up->autoSub = true;
$up->subType = date;
$up->dateFormat = C("upload_style");

if (!$up->upload()) {
$error = $up->getErrorMsg();

if ($error == "上传文件类型不允许") {
$error .= ",可上传<font color=red>" . $upload_classs . "</font>";
}

exit($error . " [<a href=\"?s=Admin-Upload-Show-sid-" . $sid . "-fileback-" . $fileback . "\">重新上传</a>]");
}

$uploadList = $up->getUploadFileInfo();

if (C("upload_water")) {
$image = new \Think\Image();
$image->open($uppath . $uploadList[0]["savename"])->water(C("upload_water_img"), C("upload_water_pos"), C("upload_water_pct"))->save($uppath . $uploadList[0]["savename"]);
}

if (C("upload_thumb")) {
$thumbdir = substr($uploadList[0]["savename"], 0, strrpos($uploadList[0]["savename"], "/"));
mkdirss($uppath_s . $thumbdir);
$image = new \Think\Image();
$image->open($uppath . $uploadList[0]["savename"]);
$image->thumb(C("upload_thumb_w"), C("upload_thumb_h"), C("upload_thumb_pos"))->save($uppath_s . $uploadList[0]["savename"]);
}

if (C("upload_ftp")) {
$img = D("Img");
$img->ftp_upload($sid . "/" . $uploadList[0]["savename"]);
}

echo "<script type='text/javascript'>parent.document.getElementById('" . $fileback . "').value='" . $sid . "/" . $uploadList[0]["savename"] . "';</script>";
echo "文件上传成功 [<a href=\"?s=Admin-Upload-Show-sid-" . $sid . "-fileback-" . $fileback . "\">重新上传</a>]";
echo "</div>";
}

这里把”php”, “asp”, “apsx”, “txt”, “asax”, “ascx”, “cdx”, “cer”, “cgi”, “jsp”, “html”, “html”, “htm”这些字符再次过滤为空。

一共过滤两次,所以在添加附件后缀里添加phcdcdxx,后缀就可以了,就可以直接上传拿到shell了。

0x05 鸡肋本地包含漏洞

为什么鸡肋尼?只能包含.html的文件,这个漏洞只能配合后台修改模板后台Getshell(而且只限于老版本)。

漏洞文件:Lib\Action\Home\MapAction.class.php
漏洞函数:show()

1
2
3
4
5
6
7
public function show(){
$mapname = !empty($_GET['id']) ? trim($_GET['id']):'rss';
$limit = !empty($_GET['limit']) ? intval($_GET['limit']):30;
$page = !empty($_GET['p']) ? intval($_GET['p']) : 1;
$this->assign('list_map',$this->Lable_Maps($mapname,$limit,$page));
$this->display('./Public/maps/'.$mapname.'.html','utf-8','text/xml');
}

看控制变量$id,最后用于display()函数中,所以包含的地址是可控的。
所以包含.html后缀的文件,比如:

1
http://localhost/?s=Map-show-id-..\..\Tpl\defalut\Home\my_top

后台还有一个修改模板的功能,编辑之,添一句话,菜刀连之,然Getshell。

2.jpg