
Detaillierte Analyse von CVE-2024-27198: Authentifizierungsumgehung in JetBrains TeamCity, die zu Remote-Code-Ausführung führt, mit Code-Level-Erklärung und Exploit-Demonstration.
TeamCity ist ein Continuous Integration and Deployment Server, der Continuous Unit Testing, Code-Qualitätsanalyse und frühzeitige Berichterstattung über Build-Probleme direkt einsatzbereit bietet.
Die Sicherheitslücke tritt in einer Bibliothek auf, die es Angreifern ermöglicht, auf beliebige nicht authentifizierte Endpunkte zuzugreifen.
Der anfällige Code befindet sich in der Bibliothek web-openapi.jar im lib-Verzeichnis.
Die Klasse jetbrains.buildServer.controllers.BaseController ist für die Verarbeitung von Anfragen und Antworten zuständig, jedoch fehlerhaft implementiert. Sehen wir uns den Code an:
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);
}
}
Der Hauptzweck der ModelAndView-Methode besteht darin, die angeforderte Seite auf der Benutzeroberfläche darzustellen. Hier beginnt der Fehler. Beachten Sie, dass updateViewIfRequestHasJspParameter aufgerufen wird, wenn unsere Anfrage nicht umgeleitet wird. Um die Ursache der Sicherheitslücke zu finden, müssen wir tiefer graben.
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);
}
}
Diese Methode aktualisiert den View-Namen des ModelAndView-Objekts, wenn bestimmte Bedingungen erfüllt sind. isControllerRequestWithViewName ist True, wenn das modelAndView-Objekt einen Namen hat und der Pfad in der URL nicht auf .jsp endet. Beispielsweise erfüllt http://localhost:8111/random_string diese Bedingungen, während http://localhost:8111/admin/admin.html oder http://localhost:8111/admin.jsp ungültig wären. Wie oben besprochen, dürfen wir nichts aufrufen, das eine Umleitung auslöst – normalerweise Seiten, die eine Autorisierung erfordern. Das Programm weist dann eine Variable namens jspFromRequest zu, die die Methode getJspFromRequest() aufruft. Wechseln wir nun zu dieser Methode; den restlichen Code erkläre ich anschließend. Es gibt eine if-Anweisung, die prüft, ob isControllerRequestWithViewName wahr ist, das Ergebnis des Aufrufs von getJspFromRequest() nicht leer ist und der Name des modelAndView-Objekts nicht gleich der Seite ist, die wir anfordern möchten.
protected String getJspFromRequest(@NotNull HttpServletRequest request) { String jspFromRequest = request.getParameter("jsp"); return jspFromRequest == null || jspFromRequest.endsWith(".jsp") && !jspFromRequest.contains("admin/") ? jspFromRequest : null; }
Diese Funktion ruft zunächst den Wert eines Request-Parameters namens jsp ab. Die Überprüfung stellt sicher, dass jsp auf .jsp enden und nicht /admin enthalten darf. In Kombination mit der if-Anweisung oben:
if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {
modelAndView.setViewName(jspFromRequest);
}
Erhalten wir ein Gesamtbild, wie die URL aussehen muss:
.jsp enthalten.jsp-Request-Parameter darf nicht gleich dem Pfad sein. Beispielsweise ist /random?jsp=/random ungültig.jsp muss auf .jsp enden.TeamCity bietet eine REST-API zur Integration externer Anwendungen und zur Erstellung von Skript-Interaktionen mit dem TeamCity-Server. Sie ermöglicht den Zugriff auf Ressourcen über URL-Pfade. Sie können mit der Arbeit mit der REST-API beginnen, indem Sie die URL
http://<TeamCity Server host>:<port>/app/rest/serverin Ihrem Browser öffnen: Diese Seite gibt einige Hinweise zur Erkundung der API.
TeamCity bietet eine REST-API, die uns den Zugriff auf sensible Ressourcen ermöglicht, wenn wir die Authentifizierung umgehen können. In diesem Fall versuchen wir beispielsweise, auf /app/rest/server zuzugreifen.

Wie üblich leitet uns der Server zu /login.html weiter, wenn wir /app/rest/server anfordern. Lassen Sie uns eine perfekte URL konstruieren, um diese Einschränkung zu umgehen. Erstens kann unser Pfad alles sein, solange er den Statuscode 404 oder sogar 200 zurückgibt, z. B. login.html. Als Nächstes verwenden wir jsp, um /app/rest/server anzufordern, jedoch muss es auf .jsp enden. In dieser Situation gibt es sogar zwei Tricks, die wir verwenden können, um diese Überprüfung zu umgehen. Wir können das Semikolon ; als Parameter-Trennzeichen verwenden: /app/rest/server;.jsp – in diesem Fall wird .jsp als zweiter Parameter behandelt. Die zweite Umgehung besteht darin, URI-Fragmente # zu verwenden: /app/rest/server%23.jsp. Was hinter dem URI-Fragment steht, wird nicht für das Routing verwendet, sondern nur zur Navigation innerhalb einer Seite. Beachten Sie, dass das Zeichen URL-kodiert werden muss, da der Browser es sonst ignoriert.

Mit dieser Technik können wir sogar einen neuen Benutzer mit Admin-Berechtigung erstellen. Die TeamCity-Dokumentation besagt, dass wir über diesen Endpunkt /app/rest/users einen neuen Benutzer erstellen können.

Gehen wir zum Admin-Panel und prüfen, ob ein neuer Benutzer erstellt wurde.

Wie erwartet wurde ein neuer Benutzer mit Admin-Berechtigung erstellt. Mit Admin-Berechtigung können wir den Server vollständig kontrollieren. Wir können jedoch noch weiter gehen, indem wir Remote-Code-Ausführung (RCE) erlangen. Diese CVE betrifft alle Versionen vor 2023.11.4, aber diese RCE ist nur für Versionen vor 2023.11 möglich. Es gibt einen undokumentierten Endpunkt /app/rest/debug/processes, der es Benutzern mit Admin-Berechtigung ermöglicht, beliebige Befehle auszuführen. Wir senden eine POST-Anfrage mit zwei Request-Parametern in der URL. Für Windows: ?exePath=cmd.exe¶ms=/c%20[unser Befehl hier] und für Linux: ?exePath=/bin/sh¶ms=-c%20[unser Befehl hier]. Ich betreibe TeamCity unter Windows, daher lautet der vollständige Pfad /app/rest/debug/processes?exePath=cmd.exe¶ms=/c%20whoami.

Der Server gibt einen 403-Fehler zurück, der besagt, dass der Anfrage ein csrf token fehlt. Die TeamCity-Dokumentation stellt uns auch den Endpunkt zum Abrufen des Tokens zur Verfügung: /authenticationTest.html?csrf.
Nachdem wir das Token abgerufen haben, fügen wir es entweder als Request-Header X-TC-CSRF-Token oder als HTTP-Parameter tc-csrf-token hinzu – hier wähle ich X-TC-CSRF-Token.

Nachdem wir das csrf token bereitgestellt haben, führen wir erfolgreich einen Befehl remote aus, was uns die vollständige Kontrolle über den Server gibt.
Bei dieser CVE lag der Sicherheitsfehler nicht in der Eingabevalidierung, sondern in der Logik des Codes. Diese Anwendung ist sehr komplex, sodass Entwickler zwangsläufig Fehler machen. Wir haben auch eine neue Technik zur Authentifizierungsumgehung gelernt. Ich hoffe, Sie haben aus dieser Analyse etwas Nützliches gelernt. Happy Hacking!!!