Hu3sky's blog

destoon前台getshell复现

Word count: 765 / Reading time: 4 min
2018/10/06 Share

漏洞分析

漏洞发生在上传头像的位置,对应文件为destoon\module\member\avatar.inc.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
<?php 
defined('IN_DESTOON') or exit('Access Denied');
login();
require DT_ROOT.'/module/'.$module.'/common.inc.php';
require DT_ROOT.'/include/post.func.php';
$avatar = useravatar($_userid, 'large', 0, 2);
switch($action) {
case 'upload':
if(!$_FILES['file']['size']) {
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
exit('{"error":1,"message":"Error FILE"}');
}
require DT_ROOT.'/include/upload.class.php';

$ext = file_ext($_FILES['file']['name']);
$name = 'avatar'.$_userid.'.'.$ext;
$file = DT_ROOT.'/file/temp/'.$name;

if(is_file($file)) file_del($file);
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');

$upload->adduserid = false;

if($upload->save()) {
...
} else {
...
}
break;

先 用$ext变量存储后缀$ext = file_ext($_FILES['file']['name']); 。接着$name变量生成并存储文件名$name = 'avatar'.$_userid.'.'.$ext;。然后用$file存储路径$file = DT_ROOT.'/file/temp/'.$name;
之后再$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); new一个upload对象。

接着我们看看upload对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class upload {
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
global $DT, $_userid;
foreach($_file as $file) {
$this->file = $file['tmp_name'];
$this->file_name = $file['name'];
$this->file_size = $file['size'];
$this->file_type = $file['type'];
$this->file_error = $file['error'];

}
$this->userid = $_userid;
$this->ext = file_ext($this->file_name);
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
$this->savepath = $savepath;
$this->savename = $savename;
}
}

用foreach依次给$_file注册为$file。并用数组的形式存储属性。$savename$savepath是直接传入参数指定。

考虑上传两个文件 第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html

1
2
3
$ext = file_ext($_FILES['file']['name']); // `$ext`即为`php` 
$name = 'avatar'.$_userid.'.'.$ext; // $name 为 'avatar'.$_userid.'.'php'
$file = DT_ROOT.'/file/temp/'.$name; // $file 即为 xx/xx/xx/xx.php

而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
1

接着 回到avatar.inc.php ,调用upload类的save方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function save() {
include load('include.lang');
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
$this->set_savepath($this->savepath);
$this->set_savename($this->savename);
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
$this->image = $this->is_image();
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
return true;
}

用了is_allow 对文件进行检测

1
2
3
4
5
6
7
8
9
10
11
function is_allow() {
if(!$this->fileformat) return false;
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
if(preg_match("/^(php|phtml|php3|php4|jsp|exe|dll|cer|shtml|shtm|asp|asa|aspx|asax|ashx|cgi|fcgi|pl)$/i", $this->ext)) return false;
if($this->savename) {
$ext = file_ext($this->savename);
if(!preg_match("/^(".$this->fileformat.")$/i", $ext)) return false;
if(preg_match("/^(php|phtml|php3|php4|jsp|exe|dll|cer|shtml|shtm|asp|asa|aspx|asax|ashx|cgi|fcgi|pl)$/i", $ext)) return false;
}
return true;
}

在第四行,对文件后缀名进行了检测。而此时的$this->extjpg 所以通过检测
接着保存文件

1
2
$this->set_savepath($this->savepath);
$this->set_savename($this->savename);

此时的savepathsavenamesaveto均以php为后缀,而$this->file实际指的是第二个jpg文件

CATALOG
  1. 1. 漏洞分析