Breaking

Jenkins Remoting RCE II – The return of the ysoserial

Jenkins Logo

Jenkins is a continuous integration server, widely used in Java environments for building automation and deployment. The project recently disclosed an unauthenticated remote code execution vulnerability discovered by Moritz Bechler. Depending on the development environment, a Jenkins server can be a critical part of the infrastructure: It often creates the application packages that later will be deployed on production application servers. If an attacker can execute arbitrary code, s/he can easily manipulate those packages and inject additional code. Another scenario would be that the attacker stealing credentials, like passwords, private keys that are used for authentication in the deployment process or similar.

The advisory on the project site gives not much detail about the problem:

“Remote code execution vulnerability in remoting module
SECURITY-232 / CVE-2016-0788
A vulnerability in the Jenkins remoting module allowed unauthenticated remote attackers to open a JRMP listener on the server hosting the Jenkins master process, which allowed arbitrary code execution.”

Jenkins remoting is used for communications between master and agent (fka “slave”) or master and CLI. (You can setup slave servers for distributed builds.) This port is activated in the default configuration and it requires no authentication for connecting and arbitrary class deserialization. 😉 Although it’s using a dynamic port number, you can find the port number in the “X-Jenkins-CLI-Port”-HTTP-header.

A look into the Jenkins GIT repository gives some more details about the problem. There is a commit that blacklists some additional classpaths:

commit baa0cef36081711d216532d562e02e2fc425d310
Author: Jesse Glick <jglick@cloudbees.com>
Date:   Wed Feb 3 17:21:11 2016 -0500

    [SECURITY-232] Blacklist RMI-related classes.

diff --git a/src/main/java/hudson/remoting/ClassFilter.java
b/src/main/java/hudson/remoting/ClassFilter.java
index 6a4e5ba..b4f54b9 100644
--- a/src/main/java/hudson/remoting/ClassFilter.java
+++ b/src/main/java/hudson/remoting/ClassFilter.java
@@ -78,7 +78,10 @@ public abstract class ClassFilter {
             LOGGER.log(Level.FINE, "Using default in built class blacklisting");
             return new RegExpClassFilter(Arrays.asList(
               Pattern.compile("^org\\.codehaus\\.groovy\\.runtime\\..*"),
               Pattern.compile("^org\\.apache\\.commons\\.collections\\.functors\\..*"),
-              Pattern.compile(".*org\\.apache\\.xalan.*")
+              Pattern.compile(".*org\\.apache\\.xalan.*"),
+              Pattern.compile("^com\\.sun\\.jndi\\.rmi\\..*"),
+              Pattern.compile("^sun\\..*"),
+              Pattern.compile("^java\\.rmi\\..*")
              ));
         }
     }

The ClassFilter was added after the SECURITY-218/CVE-2015-8103 vulnerability disclosure in November 2015. It was one of the 0-day exploits that was released with ysoserial. The patch blocks the deserialization of classes that are in specific classpaths. The list contains classpaths that are used by the ysoserial payloads, like the classpath of the Apache Commons Collections. As you might have noticed, Jenkins is only doing a blacklisting with this patch. If you have a security background, this should be a red-flag for you and we will see that this blacklisting was bad decision.

By the time the vulnerability was disclosed, I was searching for some more information about the vulnerability and found a unit test published with the bugfixes. Instead of only checking if the blacklisted classes wouldn’t be deserialized, the unit test is exploiting the complete remote code execution. Compared to the first exploit this exploit is pretty advanced and interesting.

How is the exploit working?

As mentioned, the patch added some classpaths to the blacklist. So what’s so special about these classpaths? The package java.rmi.* contains classes for the Java Remote Method Protocol (JRMP), which are part of the Java SE. If you’re able to open such a JRMP TCP listener, you’re able to trigger deserialization on this port. And the bad thing is, that the Jenkins filter is NOT catching it, because it has nothing to do with the Jenkins Remoting implementation. Moritz Bechler, has found a gadget chain, which opens this JRMP listener and it’s exploitable via Jenkins remoting.

JRMP listener gadget chain:

UnicastRemoteObject.readObject(ObjectInputStream)
UnicastRemoteObject.reexport()
UnicastRemoteObject.exportObject(Remote, int)
UnicastRemoteObject.exportObject(Remote, UnicastServerRef)
UnicastServerRef.exportObject(Remote, Object, boolean)
LiveRef.exportObject(Target)
TCPEndpoint.exportObject(Target) TCPTransport.exportObject(Target)
TCPTransport.listen()
Exploit Process
Exploit Process

The figure above visualizing the approach: We’re connecting to the Jenkins remoting port and deliver the JRMP listener payload, which opens a port for us. Then we’re connecting to this port and delivering the commons collections payload in this connection. The payload will be executed and we have remote code execution, because no blacklist is stopping us. This is pretty straightforward, but unfortunately there is one piece missing: The JRMP listener is using the default classloader and the default classloader doesn’t know about the commons collections library. Moritz Bechler has found a pretty cool solution for this problem, too: He loads the Jenkins JarLoader via Jenkins remoting and tell the JRMP listener to use this classloader to look for classes while deserializing the commons collections payload. This needs one additional trick to make the whole thing working: We need the reference to the JarLoader we created via Jenkins remoting. The java.rmi runtime using a java.rmi.server.ObjID for that. Unfortunately, this identifier is typically properly randomized, so guessing is not possible :-/ . But Moritz Bechler found another gadget which leaks this ObjID by raising an exception. The message of this exception is transmitted to us and contains the values of the ObjID. With these values we can reference the Jenkins JarLoader in the JRMP connection and the whole exploit is working. The following figure is showing the complete approach:

Complete Exploit Process
Complete Exploit Process

A few weeks later this exploit was published to the ysoserial repository by Moritz Bechler, too. He also added some other useful payloads and exploits. If you’re testing Java applications for serialization vulnerabilities, it’s definitely worth taking a look into his work. By now it’s part of the ysoserial project. It’s might be possible that the developer of the application has done the same thing as the Jenkins developer: Blacklisting the common payloads and thinking there safe now. 😉

Cheers,
Sven

PS: I’ve ran into the issue that the exploit was only working with a Java Runtime environment lower than version 8u72. The problem and a solution is described here.

Leave a Reply

Your email address will not be published.