
TeamCity è un server di Continuous Integration e Deployment che fornisce, out-of-the-box, test unitari continui, analisi della qualità del codice e segnalazioni tempestive sui problemi di build.
La vulnerabilità è presente in una libreria che consente agli attaccanti di accedere a endpoint arbitrari non autenticati.
Il codice vulnerabile si trova nella libreria web-openapi.jar nella directory to the lib.
La classe jetbrains.buildServer.controllers.BaseController è responsabile della gestione di richieste e risposte, ma è implementata in modo improprio. Vediamo come appare il codice:
public abstract class BaseController extends AbstractController {
//////
public final ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ModelAndView modelAndView = this.doHandle(request, response);
if (modelAndView != null) {
if (modelAndView.getView() instanceof RedirectView) {
modelAndView.getModel().clear();
} else {
this.updateViewIfRequestHasJspParameter(request, modelAndView);
}
}
Lo scopo principale del metodo ModelAndView è renderizzare la pagina richiesta nell'interfaccia utente. È qui che inizia il bug. Nota che updateViewIfRequestHasJspParameter verrà chiamato se la nostra richiesta non viene reindirizzata. Per trovare la causa principale della vulnerabilità, dobbiamo indagare più a fondo.
private void updateViewIfRequestHasJspParameter(@NotNull HttpServletRequest request, @NotNull ModelAndView modelAndView) {
boolean isControllerRequestWithViewName = modelAndView.getViewName() != null && !request.getServletPath().endsWith(".jsp");
String jspFromRequest = this.getJspFromRequest(request);
if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {
modelAndView.setViewName(jspFromRequest);
}
}
Questo metodo viene usato per aggiornare il nome della vista dell'oggetto ModelAndView quando soddisfa determinate condizioni. isControllerRequestWithViewName sarà True se l'oggetto modelAndView ha un nome e il path nell'URL non termina con .jsp. Ad esempio, un URL che soddisfa queste condizioni è http://localhost:8111/random_string, mentre uno non valido sarà http://localhost:8111/admin/admin.html o http://localhost:8111/admin.jsp. Come discusso sopra, non dobbiamo accedere a qualcosa che attiva il reindirizzamento; di solito si tratta di pagine che richiedono autorizzazione. Il programma assegnerà quindi una variabile chiamata jspFromRequest, che chiamerà il metodogetJspFromRequest(). Passiamo ora a quel metodo, spiegherò il codice rimanente dopo. C'è un'istruzione if che controllerà se è true, se il risultato della chiamata al metodo non è vuoto e se il nome dell'oggetto non deve essere uguale alla pagina che vogliamo richiedere tramite
protected String getJspFromRequest(@NotNull HttpServletRequest request) { String jspFromRequest = request.getParameter("jsp"); return jspFromRequest == null || jspFromRequest.endsWith(".jsp") && !jspFromRequest.contains("admin/") ? jspFromRequest : null; }
Questa funzione recupererà prima il valore di un parametro di richiesta chiamato jsp. Il controllo garantisce che jsp debba terminare con .jsp e non debba contenere /admin. Combinando con l'istruzione if qui sopra:
if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {
modelAndView.setViewName(jspFromRequest);
}
Avremo un quadro generale di come appare l'URL:
.jspjsp non deve essere uguale al path. Ad esempio, /random?jsp=/random non sarà validojsp debba terminare con .jspTeamCity fornisce una REST API per integrare applicazioni esterne e creare interazioni script con il server TeamCity. Consente di accedere alle risorse tramite percorsi URL. Puoi iniziare a lavorare con la REST API aprendo l'URL
http://<TeamCity Server host>:<port>/app/rest/servernel tuo browser: questa pagina fornisce diversi suggerimenti per esplorare l'API.
TeamCity offre una REST API che ci consente di accedere a risorse sensibili se riusciamo a bypassare l'autenticazione. In questo caso, ad esempio, proveremo ad accedere a /app/rest/server.

Come al solito, il server ci reindirizzerà a /login.html quando richiediamo /app/rest/server. Costruiamo un URL perfetto per bypassare questa restrizione. Innanzitutto, il nostro percorso può essere qualsiasi cosa purché restituisca un codice di stato 404 o anche 200, come login.html. Successivamente, useremo jsp per richiedere /app/rest/server, ma deve terminare con .jsp. In questa situazione, ci sono ben 2 trucchi che possiamo usare per bypassare questo controllo. Possiamo usare il punto e virgola ; come delimitatore di parametri: /app/rest/server;.jsp, questa volta .jsp verrà trattato come un secondo parametro. Il secondo bypass consiste nell'usare il frammento URI #: /app/rest/server%23.jsp. Ciò che viene dopo il frammento URI non verrà usato per il routing, ma solo per la navigazione all'interno di una pagina. Nota che il carattere deve essere codificato come URL, altrimenti il browser lo ignorerà.

Con questa tecnica, possiamo persino creare un nuovo utente con privilegi di amministratore. La documentazione di TeamCity dice che possiamo creare un nuovo utente tramite questo endpoint /app/rest/users.

Andiamo nel pannello di amministrazione e controlliamo se è stato creato un nuovo utente.

Come previsto, viene creato un nuovo utente con privilegi di amministratore. Con i privilegi di amministratore, possiamo controllare completamente il server. Tuttavia, possiamo spingerci ancora oltre ottenendo l'esecuzione di codice remoto. Questa CVE è vulnerabile in tutte le versioni precedenti alla 2023.11.4, ma questa RCE è possibile solo nelle versioni precedenti alla 2023.11. C'è un endpoint non documentato /app/rest/debug/processes che consente a un utente con privilegi di amministratore di eseguire comandi arbitrari. Invieremo una richiesta POST con 2 parametri di richiesta nell'URL. Per Windows sarà ?exePath=cmd.exe¶ms=/c%20[our command here] e con Linux sarà ?exePath=/bin/sh¶ms=-c%20[our command here]. Sto eseguendo TeamCity su Windows, quindi il percorso completo sarà /app/rest/debug/processes?exePath=cmd.exe¶ms=/c%20whoami

Il server restituisce un errore 403 dicendo che alla richiesta manca il csrf token. La documentazione di TeamCity ci fornisce anche l'endpoint per recuperare il token, che è /authenticationTest.html?csrf.
Dopo aver recuperato il token, lo aggiungeremo all'header della richiesta X-TC-CSRF-Token o al parametro HTTP tc-csrf-token; qui scelgo X-TC-CSRF-Token.

Dopo aver fornito il csrf token, eseguiamo con successo il comando da remoto, ottenendo il pieno controllo del server.
In questa CVE, il difetto di sicurezza non risiedeva nella validazione degli input, ma nella logica del codice. Questa applicazione è così complicata che gli sviluppatori inevitabilmente commetteranno errori. Impariamo anche una nuova tecnica per bypassare l'autenticazione. Spero che tu abbia imparato qualcosa di utile da questa analisi. Happy hacking!!!
isControllerRequestWithViewName getJspFromRequest()modelAndView