Ywc's blog

某cms后台getshell

Word count: 1.2kReading time: 5 min
2019/01/17

准备审计

码云上下载最新的lvyecms

地址:https://gitee.com/lvyecms/lvyecms?_from=gitee_searchs

审计开始

首先看网站目录结构

lvyecms后台getshell

找到入口文件 index.php

lvyecms后台getshell

thinkphp3.2风格,具体介绍见官方文档:http://document.thinkphp.cn/manual_3_2.html

进入lvyecms/Application目录,查看存在哪些模块

lvyecms后台getshell

一般进行代码审计时,先看前台代码,再看后台代码,但lvyecms都是需要后台权限的模块,所以不用管先后顺序,直接看Template模块

lvyecms后台getshell

审计Template模块代码,可以发现自定义页面

lvyecms后台getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//增加自定义页面 
public function add() {
if (IS_POST) {
$model = D('Template/Customtemp');
if ($model->create($_POST)) {
$tempid = $model->add();
if ($tempid) {
$this->Html->createHtml((int) $tempid);
$this->success("添加自定义页面成功!", U('Custompage/index'));
} else {
$this->error("添加自定义页面失败!");
}
} else {
$this->error($model->getError());
}
} else {
$this->display();
}
}

先将数据写进数据库里,然后调用createHtml函数,下面几个自定义页面都是这样的逻辑。

跟进createHtml函数,在跟进createHtml函数之前,先要知道$this->Html这个方法是从哪里来的,跟进这个控制器的父类(Common\Controller\AdminBase):

lvyecms后台getshell

发现父类中也没有定义该函数,跟进父类的父类查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class LvyeCMS extends \Think\Controller {

//缓存
public static $Cache = array();
//当前对象
private static $_app;

public function __get($name) {
$parent = parent::__get($name);
if (empty($parent)) {
return Components::getInstance()->$name;
}
return $parent;
}

public function __construct() {
parent::__construct();
self::$_app = $this;
}
...
}

这里没有定义,但定义了__get()魔术方法,魔术方法中先调用了父类的__get的魔术方法,get方法只是获取模板显示变量的值,继续向下审计,进入下面的代码块

1
2
3
4
5
6
7
public function get($name='') {
return $this->view->get($name);
}

public function __get($name) {
return $this->get($name);
}

跟进Components类中的getInstance方法,这个方法是实例化自身对象

1
2
3
4
5
6
7
static public function getInstance($_components = array()) {
static $systemHandier;
if (empty($systemHandier)) {
$systemHandier = new Components($_components);
}
return $systemHandier;
}

上面的类中也定义了__get魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function __get($name) {
if (isset(self::$_components[$name])) {
$components = self::$_components[$name];
if (!empty($components['class'])) {
$class = $components['class'];
if ($components['path'] && !class_exists($class, false)) {
import($components['path'], PROJECT_PATH);
}
unset($components['class'], $components['path']);
$this->$name = \Think\Think::instance($class);
return $this->$name;
}
}
}

先判断$name是不是类中数组的key,是就返回一个对象,$name是Html,查看数组中对应的类在哪里,跟进:

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
static private $_components = array(
'Url' => array(
'class' => '\\Libs\\System\\Url',
'path' => 'Libs.System.Url',
),
'Cloud' => array(
'class' => '\\Libs\\System\\Cloud',
'path' => 'Libs.System.Cloud',
),
'CloudDownload' => array(
'class' => '\\Libs\\System\\CloudDownload',
'path' => 'Libs.System.CloudDownload',
),
'Html' => array(
'class' => '\\Libs\\System\\Html',
'path' => 'Libs.System.Html',
),
'UploadFile' => array(
'class' => '\\UploadFile',
),
'Dir' => array(
'class' => '\\Dir',
'path' => 'Libs.Util.Dir',
),
'Content' => array(
'class' => '\\Libs\\System\\Content',
'path' => 'Libs.System.Content',
),
'ContentOutput' => array(
'class' => '\\content_output',
),
);

回到控制器类中,查看调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function add() {
if (IS_POST) {
$model = D('Template/Customtemp');
if ($model->create($_POST)) {
$tempid = $model->add();
if ($tempid) {
$this->Html->createHtml((int) $tempid);
$this->success("添加自定义页面成功!", U('Custompage/index'));
} else {
$this->error("添加自定义页面失败!");
}
} else {
$this->error($model->getError());
}
} else {
$this->display();
}
}
...

得知调用的是createHtml这个方法
到Html.class.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
public function createHtml($data = '') {
if (empty($data)) {
if (!empty($this->data)) {
$data = $this->data;
// 重置数据
$this->data = array();
} else {
$this->error = '没有数据';
return false;
}
} else if (is_integer($data)) {
$data = M('Customtemp')->where(array('tempid' => $data))->find();
if (empty($data)) {
$this->error = '没有数据';
return false;
}
}
//模板内容
$temptext = $data['temptext'];
if (empty($temptext)) {
return true;
}
//初始化一些模板分配变量
$this->assignInitialize();
//生成文件名,包含后缀
$filename = $data['tempname'];
//生成路径
$htmlpath = SITE_PATH . $data['temppath'] . $filename;
// 页面缓存
ob_start();
ob_implicit_flush(0);
parent::show($temptext);
// 获取并清空缓存
$content = ob_get_clean();
//检查目录是否存在
if (!is_dir(dirname($htmlpath))) {
// 如果静态目录不存在 则创建
mkdir(dirname($htmlpath), 0777, true);
}
//写入文件
if (false === file_put_contents($htmlpath, $content)) {
E("自定义页面生成失败:{$htmlpath}");
}
return true;
}
....

从数据库中读取文件信息,直接生成,没有过滤。

漏洞验证

lvyecms后台getshell

lvyecms后台getshell

lvyecms后台getshell

CATALOG
  1. 1. 准备审计
  2. 2. 审计开始
  3. 3. 漏洞验证