Jenkins is the most widely used CI/CD automation platform. Its flexibility and extensibility help thousands of organizations solve many of their engineering challenges.
But for all its benefits, Jenkins is also one of the most complex CI/CD automation platforms. And this complexity introduces some security concerns, particularly around its insecure default configurations.
Jenkins’ default configurations are designed to allow functionality on initial setup of the system, and according to Jenkins’ documentation, are meant to be replaced after installation. These insecure configurations, however, often remain in production environments without the knowledge of DevOps or security teams.
Let’s explore the potential dangers arising from one of these default configurations — the build authorization configuration.
The Build Authorization Configuration in Jenkins
Running builds require authorization to perform actions in Jenkins, which requires the existence of “build authorizations,” also known as the association between a build and a user account. Permissions are used to perform the various actions that the build needs, such as allocating an agent to run the build or storing build results in the system.
Jenkins’ authorization model consists of permissions that control access to different resources and actions on various objects in the system.
Some notable permissions include:
- Overall/Administer: Allows users to do everything.
- Job/Configure: Allows users to configure jobs.
- Job/Build: Allows users to trigger builds.
- Agent/Build: Allows users to execute builds on an agent.
All other descriptions can be viewed by hovering over the permission names in Jenkins.
The SYSTEM permission — not seen in the figure above — is used by Jenkins. The permission enables access to any resource and allows the user to perform actions on any object in the system.
Jenkins default settings assign every build to “run as system.” In other words, Jenkins will assign builds to the all-powerful SYSTEM user by default, so that any action executed during the build has the permissions needed to do whatever it wants.
This is a major problem if an attacker obtains permissions to a repository linked with a pipeline — via compromised developer credentials, access tokens, SSH keys, or any other method.
If the hijacked user account has the ability to modify the Jenkinsfile — and, by extension, the Jenkins pipeline — the attacker can run malicious commands in the pipeline by executing a poisoned pipeline execution (PPE) attack. Jenkins’ default build authorization settings enable those malicious commands to run with the SYSTEM user permissions so bad actors can take actions like triggering other builds and creating new jobs.
Security Issues with Default Jenkins Configurations
Let’s walk through an example to see how these default configurations are insecure. In this example, a bad actor gains initial access to an organization’s repositories. They can push code — either to new or existing branches — that triggers a pipeline defined by a Jenkinsfile in their control, to which they will add malicious commands. As a regular user, the bad actor in our scenario only has Overall/Read and Job/Read permissions.
When Jenkinsfiles are manipulated in an attack, the attack surface consists of two elements.
- The pipeline steps that can be invoked, on which plugins are installed.
- The Groovy scripts that can run within the Groovy Sandbox whitelist — assuming it’s turned on as defined in the default configurations.
Let’s dig a little deeper into pipeline steps. To map all available pipeline steps, the attackers can:
- Try to use steps from the Pipeline Steps Reference to see if they work. Most likely, the set of plugins recommended by Jenkins during its installation will be available.
- Look for steps used inside the organization’s Jenkinfiles stored in accessible source code management (SCM) repositories.
- Write a script to parse all build outputs that they can read and look for more available steps.
Let’s look at some example attack scenarios.
Attack Scenario: Running Builds on Agents
Users with Overall/Read permissions can view all agent names and their labels. Bad actors can also discover agent labels by looking for them in accessible SCM repositories.
Using this knowledge, bad actors can execute a Direct-PPE attack and change the agent label in the Jenkinsfile. They can then run builds on any agent they choose, due to the fact that the default Jenkins configuration is to run builds as the SYSTEM user.
By running their build on different agents, bad actors can perform multiple actions. They can find valuable files created by previous jobs running on that agent and extract sensitive environment variables that were loaded by other builds.
Bad actors can also gain extensive network access, which can be used for lateral movement through the organization’s assets. And finally, they can leverage the agent’s identity. For example, bad actors can use an agent running on an AWS EC2 instance to obtain the permissions of the EC2 instance role so they can perform actions against the AWS account.
Attack Scenario: Triggering Builds
Bad actors can exploit the default build authorization permissions to trigger any build job they choose using the build step. With this step, bad actors can also provide it with any parameter they want if it accepts parameters.
The build step requires the Job/Build permission and can be used as seen below:
For this step, you only need to know the target job name, which is a likely scenario in most environments because global Job/Read permissions are frequently given to all users. It’s also possible to discover job names by checking pull requests in the organization’s SCM repositories, which may contain the job name that a request triggers.
A bad actor who’s able to run build jobs can then carry out malicious deployments by either:
- Deploying malicious code that was previously pushed to the codebase.
- Triggering a deployment pipeline with parameters controlled by the attacker that will ultimately change the behavior of the application.
Securing Default Configurations in Jenkins
To protect your organization from the insecure build authorization configuration in Jenkins, you’ll want to follow two steps. First, you’ll need to make sure that new builds do not automatically run as SYSTEM, but rather as a specific user. Then, you’ll need to confirm that each user has only the minimum possible permissions required to execute builds.
Step 1: Ensure that new builds run as the specified user. To do this, we can use the Authorize Project plugin.
The Authorize Project plugin allows us to define both default and per-project build authorization. It offers the following four options:
- Run as the user who triggered the build
- Run as anonymous
- Run as the specified user
- Run as SYSTEM
Builds can be triggered either manually or by a series of “automatic” triggers —such as “build periodically,” “discover pull requests,” “poll SCM,” and so on. When triggered manually, the build will run as the user who manually initiated it. Every time a build is triggered by an automatic method, it will run as whatever has been defined by the system as the “default” build authorization user.
That said, if “per-project build authorization” has been defined for that project, the build will run as the designated user regardless of whether it was triggered automatically or manually.
The plugin should therefore be used not only to ensure that the default user is no longer defined as SYSTEM, but also to reduce the attack surface by configuring per-project build authorization so that different users have explicit permissions for specific projects whenever possible.
Per-project build authorization can be configured for Pipelines, Freestyle and Multi-configuration projects in the Jenkins’ project screen under “Authorization”. Note that it cannot currently be configured for Multibranch Pipelines. Since Multibranch Pipelines are a widely used feature of Jenkins, not being able to configure per-project build authorization poses a major security issue. If you’d like to encourage the contributors of Jenkins to provide a solution, feel free to vote on the issue.
Step 2: Ensure that each user has the minimum possible permissions that they need to execute builds. To achieve this goal, we can use the Role-Based Authorization Strategy plugin.
This plugin allows system administrators to create roles with specific permissions and then assign them to users or groups. The roles are divided into three categories:
- Item roles: Allow users to perform the actions specified, but only on the items — such as projects and credentials — that match the specified regex.
- Node roles: Allow users to perform the actions specified, but only on the agents that match the specified regex.
- Global roles: Allow users to perform the specified action on all objects in the system.
Once the roles have been configured, they can then be assigned to users or groups according to the authentication method in use. And to minimize user privileges, it’s a best practice to assign human users and build user-specific, limited roles using this plugin.
If you’re looking for another plugin to address this issue, you may want to learn more about the Matrix Authorization Strategy plugin.
How to Configure Secure Jenkins Build Authorization Strategies
To protect yourself from Jenkins’ insecure default configuration, we recommend that you configure an alternate default build authorization strategy and per-project build authorization strategy. Let’s walk through how you can do this with the two plugins outlined above.
First, ensure that the Authorize Project plugin is installed. Because the plugin doesn’t support configuring per-project authorization for Multibranch Pipelines, configure the “Project Default Build Authorization” under “Configure Global Security” to use a specific user for all Multibranch Pipelines. That user should not be used by a human, and it should usually have only Agent/Build for specific nodes and Job/Read for all Multibranch Pipelines configured using the Role-Based Authorization Strategy plugin.
Now you can configure “Per-project configurable Build Authorization” under “Configure Global Security” to enable one or more of the following: “run as specific user”, “run as anonymous” and “run as user who triggered build”. Please note that “run as specific user” is the best option in terms of both security and function.
Then, for Pipelines or Freestyle jobs, configure each job’s “Authorization” in the job’s screen to “run as specific user.” The user should be specially created for that job or group of jobs using an item and node roles that consist of minimal privileges such as Agent/Build and Job/Read only.
Best Practices for Configuring User Permissions in Jenkins
Use the Role-based Authorization Strategy plugin to configure the following settings:
- Add all users except administrators to a global role that has only the Overall/Read permission. If more global roles are needed, assign them to a minimum number of users.
- Because roles are defined using regular expressions, we recommend that you enforce project naming conventions so it’s easy to configure granular roles.
- Create specific roles for both items — such as Jobs and Credentials — and agents with patterns matching the projects and nodes to which they need access.
- Because some permissions pose more risk, avoid assigning users with the following permissions: Job/Configure, Job/Delete, Job/Create, Job/Move, Run/Delete, Run/Replay, Agent/Configure, Agent/Connect, Agent/Delete, Agent/Disconnect and Agent/Provision.
- Consider removing the Job/Build permission from users to prevent a scenario in which a specific pipeline is tampered with and is then executed by unsuspecting privileged users. Removing the Job/Build permission prevents the compromised build from having elevated privileges.
Because there are a lot of steps required to protect your organization from the insecure build authorization configuration, you may want to try the Configuration as Code plugin. to simplify the process considerably. Jenkins’ Configuration as Code simplifies this process and enables you to define Jenkins configurations as a simple yaml syntax.
Securing Your CI/CD Pipelines
Because Jenkins configurations are dynamic, it’s important to ensure that your recommended configurations remain unchanged and that you’re getting the visibility you need to identify changes to your attack surface stemming from your CI/CD pipelines. To learn more about securing your pipelines with Prisma Cloud, request a free trial.