LooselyTyped

(map blog thoughts)

Oct 25, 2017 - devops

Uncovering passwords in Jenkins

Imagine a situation where you are handed a Jenkins installation with no knowledge of how it was actually set up. One can establish the overall setup via the "Manage Jenkins" screen, looking over the plugins that have been installed and reviewing each of the jobs via their configuration screen. However, one point of concern are the credentials being used within Jenkins. How does one recover a lost or forgotten password?

Disclaimer While there are legitimate reasons why you may need to follow the advice in this post, I encourage you to not let the script kiddie in you tempt you in doing something malicious or unauthorized.

Starting point

The first thing we will need is access to the Jenkins installation itself. If you do not have this you should stop reading now and save yourself some time :)

Start by SSH-ing into the Jenkins server, and locate JENKINS_HOME. Typically this is set to ~/.jenkins or /var/jenkins_home. Considering JENKINS_HOME is an environment variable, simply env-ing at the command prompt should get you what you need.

Next, we need to figure out where in Jenkins is the password stored. Jenkins uses a few different locations to store credentials, depending on whether they are system level or job specific. A couple of files and directories that store credentials are

  • $JENKINS_HOME/credentials.xml

    This is the file the Credentials plugin uses to store credentials.

  • JENKINS_HOME/jobs/

    This is the directory where Jenkins stores all of the jobs you have configured, along with their respective config.xml files. Within these files you will find any credentials that you supplied as part of the job configuration. For example, the Artifactory plugin allows you to Override default credentials on a per job basis. [1]. Any such overrides are stored in the jobs configuration, and you will find them (encrypted of course) here.

Usually, a grep in $JENKINS_HOME with the username whose credentials you wish to uncover is the fastest path to finding out where you ought to go looking.

Determining the class that is used for encryption/decryption

Now that you have a file to work with, the next step is to figure out how to go about decrypting the password. Thankfully Jenkins stores almost all of its configuration in XML files, so you can either view the file via cat or less, or simply scp the file to your local machine and work with it using your favorite text editor.

For discussion sake, let us say that the username happens to be jenkins-user@mycompany.com and you find that the credentials you seek are indeed stored in credentials.xml. The file might reveal a snippet as shown:

credentials.xml
<username>jenkins-user@mycompany.com</username>
<password>{AQAAABAAAAAgyqp9mI73xTYaYkaMRNolxwxR+X0qev7q6Hb3KcchbM9VA5ERj0RG1Nrl/aFw7haU}</password>
The snippet you might find may be different than the one shown, but as long as you see your username this is where you want to be at.

Ah! There it is …​ The password just sits there …​ taunting you, waiting to be uncovered.

Now, start working your way up the tag hierarchy till you come across a tag that looks like a fully qualified Java class name, perhaps, something like this:

credentials.xml
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
  <scope>SYSTEM</scope>
  <id>5dc3bbe5-e5a6-496e-a616-199a207d8122</id>
  <description>This is a user</description>
  <username>jenkins-user@mycompany.com</username>
  <password>{AQAAABAAAAAgyqp9mI73xTYaYkaMRNolxwxR+X0qev7q6Hb3KcchbM9VA5ERj0RG1Nrl/aFw7haU}</password>
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>

Cool! So now we know the class that Jenkins instantiates to support this property.

Now, we need to find the source code, and establish what mechanism this class uses to encrypt/decrypt the password. Use your favorite search engine to locate the source, in our case we simply search for com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl.

A quick review of the source reveals this constructor:

UsernamePasswordCredentialsImpl constructor
@DataBoundConstructor
@SuppressWarnings("unused") // by stapler
public UsernamePasswordCredentialsImpl(@CheckForNull CredentialsScope scope,
                                       @CheckForNull String id, @CheckForNull String description,
                                       @CheckForNull String username, @CheckForNull String password) {
    super(scope, id, description);
    this.username = Util.fixNull(username);
    this.password = Secret.fromString(password);
}

Ah! So com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl uses hudson.util.Secret to encrypt the password. Next stop, hudson.util.Secrets source code, which in turn reveals a decrypt method!

Secret#decrypt
@CheckForNull
public static Secret decrypt(@CheckForNull String data) {
  // code left out for brevity's sake.
}

We now know what class is used to encrypt/decrypt the password, the only question that remains is how do we use it? We could try to simplify the implementation and somehow manage to run it on it’s own, maybe in a test class with a main method, but that is certainly painful especially if this class has dependencies on other classes.

What we need is a runtime where we can load this class, and all of its dependencies and simply invoke hudson.util.Secret#decrypt passing in the password we found in credentials.xml.

Guess what? We have the runtime already available to us. It’s Jenkins itself!

Using the Script Console to uncover the password

Jenkins ships with a Script Console which we can use to arbitrarily run Groovy scripts [2]. In the script console we could simply run the following:

Script Console
import hudson.util.Secret;

println Secret.decrypt("{AQAAABAAAAAgyqp9mI73xTYaYkaMRNolxwxR+X0qev7q6Hb3KcchbM9VA5ERj0RG1Nrl/aFw7haU}");

// outputs super_secret_password!

Woot! There you have it. We have our camouflaged password available to us in plain text!

That’s pretty much all there is to it.

Running your own experiments

Experimenting with this need not be a perilous affair involving your production-grade Jenkins installation. You can try all of the above if you run Jenkins in Docker. Here are the steps

  • Run Jenkins in Docker with docker run -p 8080:8080 --name myjenkins jenkins

  • Keep an eye on the logs because Jenkins will spit out the default admin login that you will need to log in initially. You should see something like this in the logs

    Jenkins Log
    Jenkins initial setup is required. An admin user has been created and a password generated.
    Please use the following password to proceed to installation:
    
    016b9b01454f418caf2dab842474b351
    
    This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
  • Now, navigate to http://localhost:8080/, enter the administrator password you garnered from the log.

  • This should bring up Jenkins "Customize Jenkins" screen Simply hit the X on the top left (we will install the plugins we need for this experiment), and then on the next screen click "Start using Jenkins"

  • Next, navigate to the "Plugin Manager" and install the Github Plugin. Be sure to restart Jenkins right after installation completes

  • Once Jenkins has restarted, navigate to "Configure System", click on "Add Github Server" under the "Github" section It does not matter what you put in here — our intent is to simply put in enough information to appease Jenkins. Next to "Credentials" pick "Jenkins" from the "Add" pulldown, and add a "Username with password" credential. Again it does not matter what you put here - the idea is to simply have Jenkins store a set of credentials for us.

That’s all the setup you need. Now you can get to the Jenkins installation directory using docker exec like so

Docker exec
# we use the same "name" as provided docker run with
$ docker exec -it myjenkins /bin/bash

# inside the container,
> cd /var/jenkins_home/

Now go exploring!

It must be noted that the Github Plugin depends on the Credentials Plugin, which in turn, as I mentioned earlier, stores the credentials in credentials.xml.

Summary

In short, here are the requirements if you ever need to do this

  • You will need administrative access to Jenkins (without which you can’t get to the Script Console) as well admin privileges to the Jenkins installation itself

  • Then, you will need to determine exactly where the username/password are stored I have listed a few locations in this post where you ought to go looking, and as I have mentioned before, never underestimate the power of simple grep

  • Once you have the location, you will need to determine what class Jenkins uses for that particular property, and trace its source to find the class and method used to encrypt/decrypt the password

  • Finally, use the "Script Console" to use that particular class and associated decrypt method along with the password you find on disk to get the real password in plain text

Parting thoughts

The intent of this post is not to in any way encourage malicious behavior, and thus should not be used for that purpose. However, now that we now know that passwords in Jenkins, while encrypted at rest, can be easily decrypted, should allude to a few important points of consideration, namely

  • The number of people who have access to the Jenkins installation should be limited. This is not only about limiting the number of prying eyes, but also reduces how many discrepancies crop up in the Jenkins setup. In other words, if the number of people involved in configuring Jenkins is limited, then it restricts the number of times someone erroneously adds (undesired and unapproved) credentials into Jenkins, and consequently reducing the need to ever revisit this post again.

  • The most important takeaway from this post should be that you should never use actual users' credentials in Jenkins, particularly if the credentials are tied in any way to any centralized domain management system like Active Directory Always create system users to allow Jenkins to authenticate with other VCS systems like Github/Gitlab, or artifact management systems like Nexus/Artifactory

    Outside of the security concern here, this has several other benefits Most domain management systems institutionalize a password expiration period, and you really don’t want to have to go resetting Jenkins passwords every 90 days (or whatever the standard policy is) Lastly, most domain services will expire a users' credentials if they were to ever leave the organization (or move say from a contractor to a FTE - Yes, this has happened) and suddenly Jenkins fails to authenticate itself to say Github.


1. While this may seem like a good idea initially, or you might decide to use this facility to quickly set up a job, it’s not something you want to do or encourage. You will be best served by configuring the plugin locally in the Jenkins Configuration screen, which centralizes the configuration, allowing job creators to simply use whatever is available to them. This also reduces the chances of "snowflake" jobs, allowing for better manageability and changing things if and when needed.
2. You will need admin privileges on Jenkins itself to get to the Script console — with good reason I might add.