qtc-de / beanshooter

JMX enumeration and attacking tool.
GNU General Public License v3.0
378 stars 45 forks source link

Connect to server with custom SSL certificate? #24

Closed ret2src closed 1 year ago

ret2src commented 1 year ago

During a recent penetration test we have discovered a JMX service that allows connections using a custom keystore file. Apparently, Nessus was able to successfully connect to the service and extract information such as the correct VM arguments of the JVM process.

In their research blog, Tenable write:

The vulnerability can be exploited in the following manner:

1. Store JMX server certificate in Java keystore. (Note: this can be retrieved remotely)
2. Connect to JMX/RMI over TCP port 9091 using 'jconsole'.
3. A user interface is now available to inspect Java Memory, Threads, Classes, VM Summary, and MBeans.
4. In particular, the startup command listed above can be viewed in the VM Summary tab. Note that this contains the keystore password in plain text.

While we were able to establish a connection after extracting the keystore and truststore files from the server (we already compromised a Domain Admin) and using the keystore password reported by Nessus, we did not find a way to retrieve the required JMX certificate remotely without authentication.

Since beanshooter apparently also misses this functionality, we receive the following stack trace upon establishing a connection:

$ java -jar beanshooter-3.0.0-jar-with-dependencies.jar enum 13.33.33.37 9091
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[+] Checking for unauthorized access:
[+]
[-] Caught java.security.cert.CertPathValidatorException while invoking the newClient method.
[-] The exception occured unexpected and was not caught by beanshooter.
[-] Please report the exception to help improving the exception handling :)
[-] StackTrace:
java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: 
        javax.net.ssl.SSLHandshakeException: Certificates do not conform to algorithm constraints
        at java.rmi/sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:308)
        at java.rmi/sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:204)
        at java.rmi/sun.rmi.server.UnicastRef.invoke(UnicastRef.java:133)
        at java.management.rmi/javax.management.remote.rmi.RMIServerImpl_Stub.newClient(RMIServerImpl_Stub.java:83)
        at java.management.rmi/javax.management.remote.rmi.RMIConnector.getConnection(RMIConnector.java:2107)
        at java.management.rmi/javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:321)
        at java.management.rmi/javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:265)
        at de.qtc.beanshooter.plugin.providers.RMIProvider.getMBeanServerConnection(RMIProvider.java:68)
        at de.qtc.beanshooter.plugin.PluginSystem.getMBeanServerConnectionUmanaged(PluginSystem.java:211)
        at de.qtc.beanshooter.operation.EnumHelper.enumAccess(EnumHelper.java:154)
        at de.qtc.beanshooter.operation.Dispatcher.enumerate(Dispatcher.java:147)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at de.qtc.beanshooter.operation.BeanshooterOperation.invoke(BeanshooterOperation.java:348)
        at de.qtc.beanshooter.Starter.main(Starter.java:22)
Caused by: javax.net.ssl.SSLHandshakeException: Certificates do not conform to algorithm constraints
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:371)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:314)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:309)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
        at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
        at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:480)
        at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:458)
        at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:201)
        at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
        at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1505)
        at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1420)
        at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:455)
        at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:920)
        at java.base/sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:1290)
        at java.base/java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:81)
        at java.base/java.io.BufferedOutputStream.flush(BufferedOutputStream.java:142)
        at java.base/java.io.DataOutputStream.flush(DataOutputStream.java:128)
        at java.rmi/sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:230)
        ... 16 more
Caused by: java.security.cert.CertificateException: Certificates do not conform to algorithm constraints
        at java.base/sun.security.ssl.AbstractTrustManagerWrapper.checkAlgorithmConstraints(SSLContextImpl.java:1573)
        at java.base/sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(SSLContextImpl.java:1498)
        at java.base/sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:1442)
        at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:638)
        ... 32 more
Caused by: java.security.cert.CertPathValidatorException: Algorithm constraints check failed on signature algorithm: MD5withRSA
        at java.base/sun.security.provider.certpath.AlgorithmChecker.check(AlgorithmChecker.java:237)
        at java.base/sun.security.ssl.AbstractTrustManagerWrapper.checkAlgorithmConstraints(SSLContextImpl.java:1569)
        ... 35 more
[-] Cannot continue from here.

As indicated in the output of beanshooter, you might want to improve the exception handling accordingly. Furthermore, if you're up for the challenge, you might want to help us look into where to get the required certificate and make beanshooter even more awesome... ;-)

ret2src commented 1 year ago

We were able to find out how to establish a connection. You might want to automate and add this technique to beanshooter:

  1. Use nmap to check which ports might give you the required SSL certificate:
$ sudo nmap -Pn -n -p- -sVC 13.33.33.37

Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-12 02:41 EST

Nmap scan report for 13.33.33.37
Host is up (0.0016s latency).

PORT      STATE SERVICE       VERSION
[...]
9091/tcp  open  java-rmi      Java RMI
| rmi-dumpregistry: 
|   jmxrmi
|     javax.management.remote.rmi.RMIServerImpl_Stub
|     @13.33.33.37:49735
|     extends
|       java.rmi.server.RemoteStub
|       extends
|_        java.rmi.server.RemoteObject
18888/tcp open  java-rmi      Java RMI
|_rmi-dumpregistry: ERROR: Script execution failed (use -d to debug)
[...]
21190/tcp open  java-rmi      Java RMI
| rmi-dumpregistry: 
|   DMSFileService
|      implements org.springframework.remoting.rmi.RmiInvocationHandler, 
|     extends
|       java.lang.reflect.Proxy
|       fields
|           Ljava/lang/reflect/InvocationHandler; h
|             java.rmi.server.RemoteObjectInvocationHandler
|             @13.33.33.37:49739
|             extends
|               java.rmi.server.RemoteObject
|   DMSProcessService
|      implements org.springframework.remoting.rmi.RmiInvocationHandler, 
|     extends
|       java.lang.reflect.Proxy
|       fields
|           Ljava/lang/reflect/InvocationHandler; h
|             java.rmi.server.RemoteObjectInvocationHandler
|             @13.33.33.37:49739
|             extends
|               java.rmi.server.RemoteObject
|   DMSLogCollectionService
|      implements org.springframework.remoting.rmi.RmiInvocationHandler, 
|     extends
|       java.lang.reflect.Proxy
|       fields
|           Ljava/lang/reflect/InvocationHandler; h
|             java.rmi.server.RemoteObjectInvocationHandler
|             @13.33.33.37:49739
|             extends
|_              java.rmi.server.RemoteObject
21191/tcp open  java-rmi      Java RMI
|_rmi-dumpregistry: ERROR: Script execution failed (use -d to debug)
[...]
49735/tcp open  ssl/unknown
| ssl-cert: Subject: commonName=iMC Development Team/organizationName=Hangzhou H3C Technologies Co,. Ltd./stateOrProvinceName=Beijing/countryName=CN
| Not valid before: 2007-03-28T03:53:34
|_Not valid after:  2022-03-28T03:53:34
|_ssl-date: 2023-01-12T07:43:14+00:00; -1s from scanner time.

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 102.22 seconds
  1. As we can see from the output above, port 49735/tcp has something to do with the JMX service on port 9091/tcp. Let's download its certificate to a file:
$ openssl s_client -connect 13.33.33.37:49735 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > cert
Can't use SSL_get_servername
depth=0 C = CN, ST = Beijing, L = Shang-Di Information Industry Base, O = "Hangzhou H3C Technologies Co,. Ltd.", OU = R&D Beijing, CN = iMC Development Team
verify error:num=18:self-signed certificate
verify return:1
depth=0 C = CN, ST = Beijing, L = Shang-Di Information Industry Base, O = "Hangzhou H3C Technologies Co,. Ltd.", OU = R&D Beijing, CN = iMC Development Team
verify error:num=10:certificate has expired
notAfter=Mar 28 03:53:34 2022 GMT
verify return:1
depth=0 C = CN, ST = Beijing, L = Shang-Di Information Industry Base, O = "Hangzhou H3C Technologies Co,. Ltd.", OU = R&D Beijing, CN = iMC Development Team
notAfter=Mar 28 03:53:34 2022 GMT
verify return:1
DONE
  1. Create a Java truststore file:
$ keytool -import -noprompt -alias CN -file cert -keystore truststore -storepass 123456 
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Certificate was added to keystore

Warning:
The input uses the MD5withRSA signature algorithm which is considered a security risk and is disabled.
The input uses a 1024-bit RSA key which is considered a security risk. This key size will be disabled in a future update.
  1. Open jconnect using the truststore file:
$ jconsole -J-Djavax.net.ssl.trustStore=truststore -J-Djavax.net.ssl.trustStorePassword=123456   
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
  1. If the connection succeeds, you can attempt to further exploit the underlying system.
qtc-de commented 1 year ago

Hi @ret2src :wave:

thanks for reporting and the detailed information :+1: The exception is now catched and a corresponding error message is displayed.

Fully solving this exception is really a challenge. The underlying issue is not that the certificate is missing (beanshooter does not care about certificates at all), but that the server certificate uses disabled cryptographic algorithms. These are configured within the java.security policy and the current default settings look like this:

jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \
    RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224, \
    SHA1 usage SignedJAR & denyAfter 2019-01-01, \
    include jdk.disabled.namedCurves

Adjusting these settings at runtime is rather difficult. The correct way to work around them would probably to create a custom CertPathValidator, that actually does nothing, and register it as security provider. However, this would be quite some implementation effort for an edge case that should not appear to often.

I'm actually surprised that adding the server certificate to the truststore worked. This may be the case because the signature algorithm was the problem in your situation and Java does not need to validate the signature if the certificate was explicitly set trusted. However, I do not expect it to work when e.g. the RSA key size is to small.

I will evaluate how much effort a custom CertPathValidator would be and whether it actually works. If it does, I may add it in future. For now, only the exception handling was improved. Again, thanks for reporting :)