DedeCMS前台鸡助Getshell漏洞
phpoop 漏洞分析 20272浏览 · 2018-01-22 07:13

0x00 介绍

织梦5.7会员中心,由于为了安全性问题,限制了注册会员在会员中心发布信息的时候上传图片,但是管理员登录会员中心发布信息的时候上传图片却不受影响。那该如何解决呢?下面我们来说明一下具体的解决方案。

首先,具体的问题为,注册会员点击图片上传,预览选择好本地图后点击上传到服务器上,会出现如下所示结果:

图片上传失败,并无像正常上传图片后提交按钮跳转到相应的图像属性界面上,仅在当前窗口上弹出一个滚动条,上面的滚动条里面提示为“提示:需输入后台管理目录才能登陆”,但因滚动条高度受限制了所以我们看不到提示。想要查看具体的提升信息的话,请点击向下的滚动条一直往下,即可出现文字提示。

现在知道具体原因后就容易解决问题了,直接搜索织梦网站程序文件夹下的全部包含“提示:需输入后台管理目录才能登陆“的文件,找到include\dialog\config.php文件。其中有段代码

//检验用户登录状态
$cuserLogin = new userLogin();

if($cuserLogin->getUserID() <=0 )
{
    if(empty($adminDirHand))
    {
        ShowMsg("<b>提示:需输入后台管理目录才能登录</b><br /><form>请输入后台管理目录名:<input type='hidden' name='gotopage' value='".urlencode($dedeNowurl)."' /><input type='text' name='adminDirHand' value='dede' style='width:120px;' /><input style='width:80px;' type='submit' name='sbt' value='转入登录' /></form>", "javascript:;");
        exit();
    }
    $adminDirHand = HtmlReplace($adminDirHand, 1);
    $gurl = "../../{$adminDirHand}/login.php?gotopage=".urlencode($dedeNowurl);
    echo "<script language='javascript'>location='$gurl';</script>";
    exit();
}

所以说dedecms5.7要上传图片的话,必须按照上面做,我们这里是按照这个规则,认为管理员开启了会员上传图片的权限,低于5.7的只要开启会员中心即可日传

扎心哦~~~~

0x01 准备工作

1,安装新版cms

地址:http://updatenew.dedecms.com/base-v57/package/DedeCMS-V5.7-UTF8-SP2.tar.gz

2, 打开dede会员功能

3,注册一个新用户

4,审核一下此用户

嗯~~~~这样一个基本的站点就完成了。我们也可以进行测试了

0x01 利用过程

1,先添加一个图片马

上图这个是经过处理的图片马,需要处理的图片马是因为(绕过文件后缀名检测以后,php-GD对图片的渲染和处理会导致webshell代码错位失效,所以需要特殊的图片马进行绕过,图片马的制作)

2,

0x02 图片马制作

首先感谢先知安全的文章,没有先知的文章还真的绕不过去。。。

这里确实不要多说什么,因为绕过文件后缀名检测以后,php-GD对图片的渲染和处理会导致webshell代码错位失效,所以我们需要进行绕过。

绕过php-GD对图片的渲染和处理导致webshell代码错位失效(此处参考索马里海盗方法)

图片会经过php-GD处理,会导致webshell语句错位失效,如何在处理后仍然保留shell语句呢?

在正常图片中插入shell并无视GD图像库的处理,常规方法有两种
1.对比两张经过php-gd库转换过的gif图片,如果其中存在相同之处,这就证明这部分图片数据不会经过转换。然后我可以注入代码到这部分图片文件中,最终实现远程代码执行
2.利用php-gd算法上的问题进行绕过

这里我们选择第二种,使用脚本进行处理图片并绕过
1、上传一张jpg图片,然后把网站处理完的图片再下回来 比如x.jpg
2、执行图片处理脚本脚本进行处理 php jpg_payload.php x.jpg
3、如果没出错的话,新生成的文件再次经过gd库处理后,仍然能保留webshell代码语句

提示:
1、图片找的稍微大一点 成功率更高
2、shell语句越短成功率越高
3、一张图片不行就换一张 不要死磕
注:上面的字全部是抄的,先说明一下不然给人按在地上骂就不好了
制作过程:



0x03 漏洞原理

漏洞地址:http://127.0.0.1/DedeCMS-V5.7-UTF8-SP2/uploads/include/dialog/select_images_post.php

漏洞文件:select_images_post.php












0x04 漏洞修复

前面有提到过,没有?没看到?说的不清不楚?等官方更新版本咯。

参考:http://mp.weixin.qq.com/s/IZn_xnO2tyUWmx9dronYUQ
7 条评论
某人
表情
可输入 255
rооt
2018-01-23 10:17 0 回复

貌似admin账号不允许前台登录。


0r3ak
2018-01-23 03:15 1 回复

@phpice 说得没错,这里很容易被误导,昨天我也纳闷,之前在复现“DeDecms 任意用户登录,管理员密码重置漏洞”的时候以为可以利用组合漏洞去getshell,不需要知道后台,后来测试的时候发现并没有用,还是提示登录后台,之前也发现了这个漏洞,我第一次测试这个“前台getshell”的时候是9月份,如果去注销后台登录后 再在前台上传是不行的了。


hades
2018-01-23 01:33 0 回复

@phpice 后台再次编辑有问题,先把你的回复用我的账号发,晚点给你恢复过来




我在16年的时候也以为是一个前台的漏洞,并提交给了先知,被驳回了,再后来我仔细测试后发现其实这是一个后台漏洞。

如果不仔细读代码确实很容易被误导。

你能在本地测试成功是因为你先登录了后台,此时已经存在了一个登录后台成功的session,然后再没用注销登录的情况下直接访问member目录,这个时候前台自动登录了admin账号,然后你想注册一个新会员,嗯,然后你前台注销登录。此时看起来好像没有任何问题,但就是这个操作让我们都以为注销成功了,其实并没有,这个时候只是注销了前台的登录,并没有注销后台的登录,回过头来,我们继续注册会员,登录会员,这个时候的所有操作都是基于前面那个登录后台成功的session进行的。贴一段代码



//检验用户登录状态
$cuserLogin = new userLogin();

if($cuserLogin->getUserID() <=0 )
{
if(empty($adminDirHand))
{
ShowMsg("<b>提示:需输入后台管理目录才能登录</b><br /><form>请输入后台管理目录名:<input type='hidden' name='gotopage' value='".urlencode($dedeNowurl)."' /><input type='text' name='adminDirHand' value='dede' style='width:120px;' /><input style='width:80px;' type='submit' name='sbt' value='转入登录' /></form>", "javascript:;");
exit();
}
$adminDirHand = HtmlReplace($adminDirHand, 1);
$gurl = "../../{$adminDirHand}/login.php?gotopage=".urlencode($dedeNowurl);
echo "<script language='javascript'>location='$gurl';</script>";
exit();
}

class userLogin
{
var $userName = '';
var $userPwd = '';
var $userID = '';
var $adminDir = '';
var $userType = '';
var $userChannel = '';
var $userPurview = '';
var $keepUserIDTag = 'dede_admin_id';
var $keepUserTypeTag = 'dede_admin_type';
var $keepUserChannelTag = 'dede_admin_channel';
var $keepUserNameTag = 'dede_admin_name';
var $keepUserPurviewTag = 'dede_admin_purview';
var $keepAdminStyleTag = 'dede_admin_style';
var $adminStyle = 'dedecms';

//php5构造函数
function __construct($admindir='')
{
global $admin_path;
if(isset($_SESSION[$this->keepUserIDTag]))
{
$this->userID = $_SESSION[$this->keepUserIDTag];
$this->userType = $_SESSION[$this->keepUserTypeTag];
$this->userChannel = $_SESSION[$this->keepUserChannelTag];
$this->userName = $_SESSION[$this->keepUserNameTag];
$this->userPurview = $_SESSION[$this->keepUserPurviewTag];
$this->adminStyle = $_SESSION[$this->keepAdminStyleTag];
}

if($admindir!='')
{
$this->adminDir = $admindir;
}
else
{
$this->adminDir = $admin_path;
}
}
/*
........
*/
function getUserID()
{
if($this->userID != '')
{
return $this->userID;
}
else
{
return -1;
}
}

通过代码可以发现 $this->userid必须不能等于空,$this->userid就是$_SESSION[$this->keepUserIDTag]

也就是$_SESSION['dede_admin_id'];

我们看看$_SESSION['dede_admin_id']是在什么情况下会被赋值



function keepUser()
{
if($this->userID != '' && $this->userType != '')
{
global $admincachefile,$adminstyle;
if(empty($adminstyle)) $adminstyle = 'dedecms';

@session_register($this->keepUserIDTag);
$_SESSION[$this->keepUserIDTag] = $this->userID;

@session_register($this->keepUserTypeTag);
$_SESSION[$this->keepUserTypeTag] = $this->userType;

@session_register($this->keepUserChannelTag);
$_SESSION[$this->keepUserChannelTag] = $this->userChannel;

@session_register($this->keepUserNameTag);
$_SESSION[$this->keepUserNameTag] = $this->userName;

@session_register($this->keepUserPurviewTag);
$_SESSION[$this->keepUserPurviewTag] = $this->userPurview;

@session_register($this->keepAdminStyleTag);
$_SESSION[$this->keepAdminStyleTag] = $adminstyle;

PutCookie('DedeUserID', $this->userID, 3600 * 24, '/');
PutCookie('DedeLoginTime', time(), 3600 * 24, '/');

$this->ReWriteAdminChannel();

return 1;
}
else
{
return -1;
}
}

在看看keepUser 是什么时候被调用的。



//登录检测
$admindirs = explode('/',str_replace("\\",'/',dirname(__FILE__)));
$admindir = $admindirs[count($admindirs)-1];
if($dopost=='login')
{
$validate = empty($validate) ? '' : strtolower(trim($validate));
$svali = strtolower(GetCkVdValue());
if(($validate=='' || $validate != $svali) && preg_match("/6/",$safe_gdopen)){
ResetVdValue();
ShowMsg('验证码不正确!','login.php',0,1000);
exit;
} else {
$cuserLogin = new userLogin($admindir);
if(!empty($userid) && !empty($pwd))
{
$res = $cuserLogin->checkUser($userid,$pwd);

//success
if($res==1)
{
$cuserLogin->keepUser();
if(!empty($gotopage))
{
ShowMsg('成功登录,正在转向管理管理主页!',$gotopage);
exit();
}
else
{
ShowMsg('成功登录,正在转向管理管理主页!',"index.php");
exit();
}
}

//error
else if($res==-1)
{
ResetVdValue();
ShowMsg('你的用户名不存在!','login.php',0,1000);
exit;
}
else
{
ResetVdValue();
ShowMsg('你的密码错误!','login.php',0,1000);
exit;
}
}

//password empty
else
{
ResetVdValue();
ShowMsg('用户和密码没填写完整!','login.php',0,1000);
exit;
}
}
}

只有在后台登录的时候被调用。前台压根就没办法对session进行操作,没有办法控制session就没有办法绕过这个if($cuserLogin->getUserID() <=0 )判断,所以,这是一个假漏洞。


========================================================================

抛开认证问题,就上传绕过来说,同目录下的select_soft_post.php文件更好绕过

把上传文件名改成1.htm.php?

文件类型只要有text就行


hades
2018-01-22 16:02 0 回复

@sqvds 漏洞发现者比较敏感 呵呵哒 我没什么的 哇哈哈


sqvds
2018-01-22 15:49 0 回复

@hades 抱歉,我只是随便说说,无所谓你们谁发现的,你太敏感了


hades
2018-01-22 14:22 0 回复

@sqvds 这个只是作为漏洞分析发出来技术探讨,难道漏洞分析的时候你们都要去找谁发现的额?? 试着想想为什么两篇文章的开头是一样的,而下面的内容不是一样的???


sqvds
2018-01-22 10:49 0 回复

搜了一下9月份好像有人发了这个洞说是和你一起审的。。