2

Destoon 20180827版本 前台getshell

 2 years ago
source link: https://chybeta.github.io/2018/09/24/Destoon-20180827%E7%89%88%E6%9C%AC-%E5%89%8D%E5%8F%B0getshell/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Destoon 20180827版本 前台getshell

发表于 2018-09-24

| 分类于 Web Security

| 阅读次数

Destoon 20180827版本 前台getshell

2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。

根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:

对应着avatar.inc.php代码如下:

<?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;

这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。

upload对象构造函数如下,include/upload.class.php:25:

<?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 as $file)来遍历初始化各项参数。而savepathsavename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。

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

$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文件。测试如下:

回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:

<?php
class upload {
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;

先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:

<?php
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;
return true;

可以看到这里仅仅对$this->ext进行了检查,如前此时$this->extjpg,检查通过。

接着会进行真正的保存。通过$this->set_savepath($this->savepath); $this->set_savename($this->savename);设置了$this->saveto,然后通过move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)file保存到$this->saveto ,注意此时的savepathsavenamesaveto均以php为后缀,而$this->file实际指的是第二个jpg文件。

综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。

然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid

不过实际利用上会有一定的限制。

第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。

第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg

省略...
$img = array();
$img[1] = $dir.'.jpg';
$img[2] = $dir.'x48.jpg';
$img[3] = $dir.'x20.jpg';
$md5 = md5($_username);
$dir = DT_ROOT.'/file/avatar/'.substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/_'.$_username;
$img[4] = $dir.'.jpg';
$img[5] = $dir.'x48.jpg';
$img[6] = $dir.'x20.jpg';
file_copy($file, $img[1]);
file_copy($file, $img[4]);
省略...

因此要利用成功就需要条件竞争了。

在upload的一开始,就进行一次后缀名的检查。其中is_image如下:

function is_image($file) {
return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));

__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。

is_allow()中增加对$this->savename的二次检查。

嘛,祝各位大师傅中秋快乐!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK