Attacking Jenkins with Shared Libraries

What is Jenkins Shared Library ?

Jenkins shared library is popular where large number of jenkins jobs or pipelines uses a repeated code in pipeline script. The developers creates certain modular functions containing the repetitive code and then reuses across various projects/pipelines/jobs. It is commonly seen in enterprise or organizations where teams work on multiple projects that shares common patterns in every pipelines. Imagine a function to send notifications to Slack about a build passed/failed can be used by many jobs.

These shared functions are often kept in some SCM like github and may or maynot be publicly accessible. As they are helper functions and does not carry any customer data/code, the scm repository is usually not private sometimes. 

What does the project structure of Jenkins Shared Library looks like?


As copied from ( https://www.jenkins.io/doc/book/pipeline/shared-libraries/ )

(root)
+- src                     # Groovy source files
|   +- org
|       +- foo
|           +- Bar.groovy  # for org.foo.Bar class
+- vars
|   +- foo.groovy          # for global 'foo' variable
|   +- foo.txt             # help for 'foo' variable
+- resources               # resource files (external libraries only)
|   +- org
|       +- foo
|           +- bar.json    # static helper data for org.foo.Bar

Most of the groovy code for these wrappers are usually placed in the vars directory. Hence if you write some wrapper code, you would place it in vars directory

You can have a look at this repository for reference https://github.com/darinpope/github-api-global-lib

Note : As an attacker if you find any codebase or SCM repository of the similar pattern, then you can confirm that you have found a Jenkins Shared Library.

Why as an attacker this could be interesting to us ?


As discussed above, the shared libraries are used in organization where there lots of jobs are running for lot of projects. Each project can have multiple jobs running against each pull request. This explains there is a great volume of jobs running. As an attacker we can get very creative here, like :

  • We can exfiltrate data from each of the build environment.
  • We can inject malicious build pipeline to put a backdoor in the artifacts.
  • Most importantly, we can compromise on large scale and remain stealthy, depending on how many jobs are executing the shared-library in their build.


Attack Methodologies 


We will discuss couple of ways by which we can attack Jenkins using the shared library.  


Attack Methodology 1 : By directly modifying the Shared Library Code

Imagine you got some credentials or access tokens that gave you access to SCM and then you got write access to a SCM repository containing the shared library code. You can navigate inside the vars directory and backdoor the groovy code from the existing library. Here for example I have added little exfiltration to helloWorld function call.

This is the original code

def call(Map config = [:]) {
            sh "echo Hello ${config.name}. Today is ${config.dayOfWeek}."
}

This is the backdoored code


We need to ensure the backdoor code fulfils some properties. 

  • The backdoor code should not break the pipeline ( should always be successful ).
  • Should leave as little footprint as possible.
In the example we have used the set +x command to suppress the bash output for each of the steps. Next we also used curl to run in silent mode to avoid those extra debug outputs.

Attack Methodology 2 : By changing the shared library location

Assume you got access to Jenkins console by some exploit or credentials. If you can see some build logs and have enough access to update configuration this method will still work. Remember, sometimes you can still get access rights to update configuration inspite of not being an Administrator. 

At first we will try to find the location of the shared library from the build logs.



Assume in this case you don't have write access to the SCM repository but you can view the code in the SCM repository. In such case what you can do is,

Create a new SCM repo, copy the code, modify the code and then update the shared library location in Jenkins with the new repository link and branch name.

Manage Jenkins -> Configure System -> Global Pipeline Libraries


Analyzing the results.

Each of the projects had a different pipeline script and each of the script executed our malicious shared-library code. All green checks represents the last build was successful.

Here is the pipeline script of  Job 1 project

@Library("shared-libraries") _
pipeline{
    agent any
    stages {
        stage("example"){
            steps{
                sh "echo Just a job1"
                helloWorld(name:"User1",dayOfWeek:"Monday")
            }
        }
    }
}

Here is the pipeline script of  Job 2 project

@Library("shared-libraries") _
pipeline{
    agent any
    stages {
        stage("example"){
            steps{
                sh "echo Just a job2"
                helloWorld(name:"User2",dayOfWeek:"Tuesday")
            }
        }
    }
}


Here is the pipeline script of  Job 3 project

@Library("shared-libraries") _
pipeline{
    agent any
    stages {
        stage("example"){
            steps{
                sh "echo Just a job3"
                helloWorld(name:"User3",dayOfWeek:"Wednesday")
            }
        }
    }
}


Now we can review our request catcher output window


We found the requests initiated by the curl and exfiltrated the environment variables. We can decode the base64 values and get the data.

What else can we do ?

Say for example we have a Java Enterprise Project. We can modify the Shared Library to add some pipeline script to download some JSP backdoor and add them to the project locations. In this way we will get a backdoored artifact.

What can be done for prevention?

  • All users should not have roles to change the configuration of the Shared Library Location.
  • Maintain logs and audit trails and trigger alert whenever there is a change in the Shared Library Location placeholder.
Resources:
  • https://github.com/darinpope/github-api-global-lib
  • https://www.jenkins.io/doc/book/pipeline/shared-libraries/