Recently we had the pleasure to take a look at GitHub’s Enterprise appliance. The appliance allows one to deploy the excellent GitHub web interface locally to host code on-site. Besides the well known interface, which is similar to the one hosted at github.com, the appliance ships with a separate interface called the management console, which is used for administrative tasks like the configuration of the appliance itself. This management interface is completely decoupled from the user interface.
During our assessment we focused on the management console where we found several vulnerabilities (others may have found them, too). On November 11, 2014 GitHub released a security advisory which included the most critical findings that have been fixed in GitHub Enterprise 2.0.0. Because the advisory doesn’t include any detailed information, we will discuss some of those vulnerabilities in detail.
The first weird thing we noticed was the authentication of the management console. Upon purchasing GitHub Enterprise, a customer gets a license file which has to be uploaded to the management console during initial setup. Besides being a regular license file, it has also been used for authentication. Each time an admin wanted to access the management console, he had to upload the license file to the web interface. No other authentication credentials required. This raises a variety of problems:
- Any user that needed to access the management console needed the license file.
- If unauthorized users gained access to this file, it cannot be changed, or revoked, or disabled.
- No traceability who submitted configuration changes.
Furthermore, GitHub Enterprise exposes most of the management console’s web interface features via an API. Authentication at the API is done by the MD5 of the license file which is sent to the API via a GET parameter. So any intermediate web server or (reverse) proxy will have instant admin access to your management console which can’t be revoked in any way.
According to the advisory, GitHub Enterpsie now uses password-based authentication. We haven’t had time to look at the new version yet, so we cannot say if the administrative interface supports multiple, personalized accounts. And, oh yeah, GitHub now enabled SSL in version 2.0.0 by default, which wasn’t enabled at the time of our assessment. So gaining access to the license file or just the MD5 from the GET parameter was pretty easy.
While testing for injection attacks, we were surprised to find several persistent XSS in the management interface. Due to GitHubs excessive security focus and bug bounty programs we didn’t expect to stumble across them so easily.
Finding XSS is fun but the most interesting vulnerability of the management console was a config file injection. When an admin edited settings in the administrative interface, they were inserted into config files on the file system, without any input validation. So by gaining access to the management console, one could injected arbitrary text into configuration files of multiple daemons used by GitHub Enterprise. We focused on the configuration of collectd, a daemon which collects system statistics, as this one needs root privileges to run properly. Additionally, support for plugins within the collectd configuration file made it the most promising target.
We first tried to inject a Exec plugin which should run a shell script with superuser privileges. (Un)Fortunately, collectd drops privileges while executing plugins by default. However, when we injected a Python plugin, it was indeed executed with superuser privileges! The following snippet illustrates the injection payload we sent in the collectd password field of a multipart POST request to the management console:
Content-Disposition: form-data; name=”collectd[password]”
Server “localhost” “123”
<Server “localhost” “123”>
This Python plugin called the following script in /tmp/python/ernw.py:
os.system(‘cp /bin/sh /home/admin/rootshell && chmod +s /home/admin/rootshell’)
The script (obviously) deploys a shell with the setuid bit set for root in the home directory of the admin user. This allowed us to elevate our privileges after SSH’ing into the box with the default admin user. However, if one wouldn’t have access via SSH already, this exploit could do way more fancy stuff like spawning bind or reverse shells.
So if you are running collectd, you shouldn’t let users write into your configuration file without input validation as this can easily lead to arbitrary code execution and even privilege escalation via Python and probably other fancy collectd plugins. As GitHub does not intend to give customers administrative privileges on the appliance, this is to be considered a critical issue.
During our assessment we noticed a pretty cool information disclosure vulnerability/feature. When a client sends a HTTP GET request to the resource “/setup/api/ohai”, the application sends back a huge JSON object with lots of interesting information about the system. Like, e.g. the passwd file. This even works without authentication.
The advisory doesn’t mention anything about this, we guess that from GitHub’s point of view this is supposed to be some kind of a feature, not a vulnerability. We couldn’t identify any reason why this resource must be available, so if you are running GitHub Enterprise, you should consider restricting access to this resource.
The XSS and config file injections required authentication, so their exploitability was limited to authorized users. However, if an attacker would have gained access to the management console, like e.g. by capturing the MD5 of the license file or the file itself, this would have led to a complete system compromise. Due to missing SSL in the default configuration, eavesdropping and man-in-the-middle attacks could have been mounted easly.
Florian and Niklaus
PS: If you want to look at all the obfuscated Ruby code on the appliance you should keep the following information from GitHub in mind: “This obfuscation is intended to discourage GitHub Enterprise customers from making modifications to the VM. We know this ‘encryption’ is easily broken.” ;D