This is a pretty simple vulnerability, but as far as I know it's the first CVE I have been issued, so that earns it a write-up!
Jenkins is a CI tool, which has many different plugins available. Most of these plugins are approved by the Jenkins team, even if they are originally contributed by third parties. I thought it was particularly interesting to see that the Jenkins team provides install statistics for each plugin, which should give a good indication of a plugin's relative usage. While auditing the security of a Jenkins instance, I came across the Google Login plugin, as described here:
The Google Login plugin is fairly simple in purpose, it basically allows Jenkins to make use of Google's OAuth system, instead of only using local user accounts. Since anyone can easily register for a Google account, this plugin allows the system to restrict logins to users from a specific domain name. An example of someone who might use that configuration would be a company who uses Google Apps for all their employee accounts. To enable SSO, this plugin could be used to authenticate their employees against the Jenkins instance, while preventing all other users from logging in.
Analysis and Findings
More often than not, systems using OAuth have some kind of vulnerability in them, so I thought that I would look at the authentication used by this plugin a little further. Using an intercepting proxy (Burp Suite), I took a look at what would happen when I tried to authenticate to the Jenkins instance. The Jenkins instance's landing page would redirect to the Google Login plugin which would then kick off the authentication process. This was done by performing a redirect to Google's OAuth page. The URL in question was similar to the following:
In the above URL, the "client_id" parameter would be the name of Google app used to provide OAuth access. The company hosting the Jenkins instance needs some way to gain access to the user's info, and the Google Login plugin handles this by requiring a Google app client ID and secret. This can really be any app, as long as it's something that makes sense to the user when they are prompted to authenticate to it. The scope is "profile" and "email", so a user isn't giving up a lot of access, really just enough to confirm that they are who they say they are.
The second thing I noticed in the above URL is the "hd" parameter. This parameter is described here (and I assume on an OAuth page somewhere too): https://developers.google.com/identity/protocols/OpenIDConnect?hl=en#hd-param Essentially that parameter tells the Google authentication page to only accept logins from a certain domain. The warning included along with this parameter turned out to be very applicable. Since the Jenkins Google Login plugin redirects the *client* to that URL, nothing prevents me as a client from modifying it.
To test if the domain restriction was being applied correctly (server side), or incorrectly (client side via the HD parameter alone), I ran through the login sequence again in Burp Suite. This time I set Burp Suite to intercept all requests, and when the accounts.google.com OAuth URL was intercepted, I removed the "HD" parameter from the end. The Google sign-on page then allowed me to supply any account, such as my Gmail account to authenticate with. This itself isn't a weakness, as once the OAuth authentication completes, the Jenkins server could then verify my email or profile domain. Unfortunately for Jenkins, when the Google OAuth permission was granted, the access token was returned to Jenkins, and Jenkins simply accepted it as is with no further verification. For the instance I was testing, this immediately granted me admin level credentials.
To see why this happened, I took a quick look at the Google Login plugin source code, available here:
The comment "TODO: Find some way to validate the credentials." immediately caught my eye, and I initially mistakenly thought this might just be a known issue. The Jenkins team clarified that that comment actually refers to verifying the Google App Client ID and Client Secret. The issue really wasn't the existing code, but rather the code that was missing. The change actually needed was to add a verification step after a token is returned. The Jenkins team had misunderstood the purpose of the HD param, and so had not included any verification beyond checking the token signature. The change necessary was to read the domain out of the token, and ensure it actually matches the domain which users are supposed to be logging in from. This can be seen here:
September 25th - Vulnerability discovered
September 30th - Vulnerability reported to Jenkins Project
October 1st - Jenkins team unable to reproduce, additional details provided
October 5th - Jenkins team confirms reproducibility
October 7th - CVE-2015-5298 assigned
October 12th - Jenkins Project releases advisory: https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2015-10-12
November 6th - Jenkins Project issues reward for reporting issue: https://wiki.jenkins-ci.org/display/JENKINS/Rewards+for+reporting+security+issues (I got a black hoodie to do a better job of looking like a hacker)