Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/pentesting-ci-cd/teamcity-security/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,44 @@ curl -ik -X POST "$TC/app/rest/users/id:1/tokens/RPC2"

If you are assessing incident impact, check for suspicious token creation, admin account creation, plugin upload/delete events, and process execution around the exposure window.<sup>[[14]](#references)[[15]](#references)</sup>

### Unauthenticated Agent Deserialization RCE (XStream / CVE-2026-63077)

TeamCity On-Premises was also affected by an unauthenticated XStream deserialization bug fixed in **2025.11.7** and **2026.1.3**. JetBrains credits Antoni Tremblay with privately reporting the vulnerability.<sup>[[21]](#references)</sup> The important technique takeaway is that an agent session header is **not** equivalent to a TeamCity user session, and an XStream `allowTypes()` list is **not** an exclusive allowlist unless the application first reapplies `NoTypePermission.NONE`.<sup>[[22]](#references)[[23]](#references)</sup>

Practical attack flow:<sup>[[22]](#references)[[23]](#references)</sup>

1. `POST /app/agents/v1/register` to obtain a server-issued `TeamCity-AgentSessionId`.
2. `POST /app/agents/v1/commands/error` with that header and attacker-controlled XML.
3. TeamCity deserializes the request body while handling the unauthenticated agent error-report path, so gadget callbacks happen before any TeamCity user authentication is required.

Useful review heuristics from this bug:<sup>[[22]](#references)[[23]](#references)</sup>

- If XStream permissions are initialized and the application later calls only `allowTypes()`, earlier hierarchy permissions such as `Map`, `Collection`, `Map.Entry`, or `Throwable` may still stay valid.
- Inner classes are interesting gadget entry points because XStream can traverse the compiler-generated `this$0` field (serialized as `outer-class`) into the enclosing object and then keep allocating exact declared field types without naming the blocked concrete class again.
- XStream `reference` attributes can reuse a previously created object without another explicit class lookup, which is useful for smuggling a denied object into later gadget stages.
- `HashSet` + `TiedMapEntry` is still a strong callback primitive: `HashSet.add()` computes a hash, `TiedMapEntry.hashCode()` calls `getValue()`, and a FreeMarker `HashAdapter` can turn that into a JavaBean getter call such as `BasicDataSource.getConnection()`.
- A JDBC gadget that reaches attacker-controlled `connectionInitSqls` can turn deserialization into SQL execution; here HSQLDB `SCRIPT` was used to write a SQL/JSP polyglot into `../webapps/ROOT/*.jspws`.
- Always inspect `WEB-INF/web.xml` for alternate JSP-style mappings. In TeamCity, `*.jspws` goes directly to Jasper while `*.jsp` requests stay behind TeamCity's normal dispatcher checks.

Hunting ideas:<sup>[[22]](#references)[[24]](#references)</sup>

- Unauthenticated `POST` requests to `/app/agents/v1/commands/error`.
- `com.thoughtworks.xstream.converters.ConversionException` on vulnerable servers.
- `com.thoughtworks.xstream.security.ForbiddenClassException` on already patched servers.
- `BasicDataSource wrapped into f.e.b.BooleanModel` and `/linked-hash-map/entry[3]/set/org.apache.commons.collections.keyvalue.TiedMapEntry` in `teamcity-server.log`.
- Unexpected unauthorized agents, especially names beginning with `scan`.
- Fresh `.jspws` files below `webapps/ROOT` and child processes spawned by the TeamCity Java service.

Quick triage commands:<sup>[[22]](#references)[[24]](#references)</sup>

```bash
grep -RaiE '/app/agents/v1/commands/error|ConversionException|ForbiddenClassException|BasicDataSource wrapped into f.e.b.BooleanModel|org.apache.commons.collections.keyvalue.TiedMapEntry|\.jspws' /opt/TeamCity/logs 2>/dev/null
find /opt/TeamCity/webapps/ROOT -maxdepth 1 -type f -name '*.jspws' -ls 2>/dev/null
ps auxww | grep -i '[j]ava.*TeamCity'
```

For code review, verify that XStream resets permissions with `NoTypePermission.NONE` immediately before adding explicit types, audit agent-facing endpoints separately from the user-facing REST/UI auth model, and check whether any writable server-side template extension can bypass the access controls that protect the default one.<sup>[[22]](#references)[[23]](#references)</sup>

### Admin RCE By Uploading A Plugin

TeamCity server plugins are ZIP packages that extend server functionality. A System Administrator can upload a plugin from the UI under **Administration -> Plugins**, load it, and execute server-side Java code.<sup>[[11]](#references)[[13]](#references)</sup>
Expand Down Expand Up @@ -739,5 +777,9 @@ Suspicious artifact publications
- [18] [JetBrains - Kotlin DSL](https://www.jetbrains.com/help/teamcity/kotlin-dsl.html)
- [19] [JetBrains - Storing Project Settings in Version Control](https://www.jetbrains.com/help/teamcity/storing-project-settings-in-version-control.html)
- [20] [JetBrains - Managing Roles and Permissions](https://www.jetbrains.com/help/teamcity/managing-roles-and-permissions.html)
- [21] [JetBrains - Critical Security Issue Affecting TeamCity On-Premises (CVE-2026-63077)](https://blog.jetbrains.com/teamcity/2026/07/cve-2026-63077/)
- [22] [Rapid7 Analysis: Unauthenticated Remote Code Execution in JetBrains TeamCity (CVE-2026-63077)](https://www.rapid7.com/blog/post/ra-unauthenticated-rce-in-jetbrains-teamcity-cve-2026-63077/)
- [23] [Rapid7 CVE-2026-63077 proof of concept](https://github.com/sfewer-r7/CVE-2026-63077)
- [24] [JetBrains - CVE-2026-63077: Additional Guidance Following Reports of Active Exploitation](https://blog.jetbrains.com/teamcity/2026/08/cve-2026-63077-update/)

{{#include ../../banners/hacktricks-training.md}}