Schwachstellenanalyse: CVE-2023-0264

Analyse einer Impersonierungs-Schwachstelle in Keycloak

Dies ist eine übersetzte Version. Das englische Original finden Sie hier.

Keycloak is an open source identity and access management solution […] [to] secure services with minimum effort

Bei Keycloak handelt es sich um einen verbreiteten Authentifizierungsserver, der von vielen unserer Kunden verwendet wird. Als Teil unserer Arbeit analysieren wir regelmäßig aktuelle Meldungen zu Schwachstellen, um unseren Kunden notfalls auf wichtige Sicherheitsupdates hinweisen zu können. Im Zuge dessen schauten wir uns auch die Keycloak-Schwachstelle CVE-2023-0265 (CVSS Bewertung8.3) im Detail an.

In diesem Blogpost analysieren wir diese Schwachstelle, die (unter bestimmten Voraussetzungen) die Impersonierung eines anderen Keycloak-Benutzers erlaubt. Im Internet finden sich hierzu nur sehr wenige Informationen, wir wollen daher die Schwachstellen-ursache analysieren und die Ausnutzung an einer ungepatchte Keycloak Instanz demonstrieren.

CVE-2023-0264 Management Summary

Die offizielle Beschreibung für CVE-2023-0264 ist recht allgemein gehalten. Sie sagt das “ein Benutzerkonto impersoniert werden kann”, wenn den Angreifern eine “UUID” bekannt ist. Basierend auf der offiziellen CVSS Bewertung muss für eine erfolgreiche Ausnutzung eine Interaktion mit dem potenziellen Opfer erfordert. Hier die offizielle CVE-Beschreibung:

A validation error within the login flow of Keycloak allows any existing user to impersonate any other existing user within the same Keycloak realm. In the worst case an attacker with a valid user account can abuse this vulnerability to elevate their privileges to an administrative account. As a condition for this attack the attacker requires the client session ID of a target account.

Sämtliche Keycloak Instanzen vor 21.0.1 sind von der Schwachstelle betroffen. Betroffene Installationen sollten schnellstmöglich aktualisiert werden.

Patch-Analyse

Das von GitHub gereviewte Advisory verweist auf das relevante Git Commit. Dieser Commit enthält glücklicherweise nur die zur Behebung der Schwachstelle gehörenden Änderungen, was eine Analyse vereinfacht.

Schauen wir uns zuerst die Generierung eines OAuth Codes an. Der Patch erweitert den Constructor der zuständigen Klasse um das zusätzliche Argument userSessionid.

Oauth2Code.java#L58

 1public OAuth2Code(
 2    String id, 
 3    int expiration, 
 4    String nonce, 
 5    String scope, 
 6    String redirectUriParam, 
 7    String codeChallenge, 
 8    String codeChallengeMethod, 
 9    String userSessionId
10    )

Für eine Usersession werden Cookies, oder eine andere Art der Zustandsidentifikation benötigt. Dies schließt daher schon alle OAuth Flows aus, die nicht Session-gebunden sind, beispielsweise den Device Authorization Grant (RFC 8628).

Die Analyse des mit den Patch implementierten Testcases liefert uns weitere wichtige Ansatzpunkte:

Der Testcase führt zunächst eine “normale” OAuth-Anmeldung für zwei unterschiedlichen Testkonten durch (Zeilen 4, 10). Der Login-Flow erhält zwei code(s) (Zeilen 5, 11), die im Anschluss verwendet werden um Accesstokens für die beiden Accounts zu erhalten (Zeile 20).

Der code besteht aus drei Teilen, die jeweils durch einen Punkt (.) getrennt werden. In Zeile 12 bis 15 wird der mittlere Teil des ersten Codes ausgelesen und der entsprechende Teil des zweiten Codes damit ersetzt.

Abschließend wird versucht mit dem so manipulierten Code eine Accesstoken zu erhalten (Zeile 20).

SSOTest.java#L217-241

 1public void failIfUsingCodeFromADifferentSession() throws IOException {
 2	// first client user login
 3	oauth.openLoginForm();
 4	oauth.doLogin("test-user@localhost", "password");
 5	String firstCode = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
 6
 7	// second client user login
 8	OAuthClient oauth2 = new OAuthClient();
 9	oauth2.init(driver2);
10	oauth2.doLogin("john-doh@localhost", "password");
11	String secondCode = oauth2.getCurrentQuery().get(OAuth2Constants.CODE);
12	String[] firstCodeParts = firstCode.split("\\.");
13	String[] secondCodeParts = secondCode.split("\\.");
14	secondCodeParts[1] = firstCodeParts[1];
15	secondCode = String.join(".", secondCodeParts);
16
17	OAuthClient.AccessTokenResponse tokenResponse;
18
19	try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
20		tokenResponse = oauth2.doAccessTokenRequest(secondCode, "password", client);
21	}
22
23	assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), tokenResponse.getStatusCode());
24}

Aus diesem Testcase kann man den ungefähren Ablauf eines erfolgreichen Angriffs ableiten:

  1. Zugriff auf den mittleren “Code” Teils eines anderen Nutzers. Wir nennen diesen Teil vorübergehend <identifier X>
  2. Start eines eigenen Loginvorgangs um einen gültigen code zu generieren.
  3. Ersetzen des mittleren Teils unseres Tokens mit <Identifier X aus Schritt 1> um diesen Account zu impersonieren.
  4. Profit

An diesem Punkt nehmen wir an, dass der mittlere Teil eines codes auf die ID des Nutzers verweist. Wir können das durch Analyse des Quellcodes verifizieren:

  1. code.split('.')[0]: Der erste Teil ist die ID des OAuth2Codes.
  2. code.split('.')[1] Der zweite Teil (den den wir ausstauschen)ist die Session ID des Nutzers in Form einer UUID. <identifier X> == user session ID)
  3. code.split('.')[2] Der dritte Teil beinhaltet die ClientID der aktuellen Session.

OAuth2CodeParser.java#L54-L65

1public static String persistCode(KeycloakSession session, AuthenticatedClientSessionModel clientSession, OAuth2Code codeData) {
2	[...]
3	String key = codeData.getId();
4	return key + "." + clientSession.getUserSession().getId() + "." + clientSession.getClient().getId();
5}

Die Schwachstelle wird dadurch verursacht das diese Usersession von Keycloak vor dem Erzeugen eines Accesstokens nicht geprüft wird.

Ausnutzen der Schwachstelle

Nachdem wir jetzt wissen was wir wo austauschen müssen können wir uns ansehen wie wir hierdurch unsere Rechte in einem Keycloak Realm erweitern können. Beim “Realm” Konzept von Keycloak handelt es sich um einen Namensraum in dem Benutzer und Clients existieren. Standardmäßig besitzt Keycloak einen master Realm mit mindestens einem administrativen Benutzer sowie mehreren zum Management verwendete Clients.

Realm-Clients im Keycloak Admin Portal

Eine Organisation oder Firma kann mehrere Realms in einer zentralen Keycloak Instanz konfigurieren. Nehmen wir als Beispiel den Anbieter eines Schwachstellenscanners. Dieser besitzt einen security-scanner Realm mit mehreren Clients (scanner-shop, scanner-api, scanner-dashboard, test)

Voraussetzung: Gültiges Benutzerkonto

Schwachstellen, die Zugriff auf ein gültiges Benutzerkonto vorraussetzten sind oft schwerer ausnutzbar. Es existieren jedoch mehrere Szenarien bei denen auch externe Angreifer sich einen solchen Benutzer erstellen können, insbesondere bei Shops oder Benutzerportalen die Keycloak verwenden. Dieser erlauben häufig die Registrierung eigener Kundenaccounts.

In unserem vorherigen Beispiel könnten Angreifer beispielsweise über einen Testaccount registrieren, wodurch ein entsprechendes Konto im security-scanner Realm von Keycloak erzeugt wird.

Nach der Registrierung können sich die Angreifer per Keycloak authentifizieren. Dadurch erhalten Sie einen Keycloak access_token und können mit diesem auf den Client scanner-api zugreifen.

Dieser Businesscase ist recht verbreitet, insbesondere bei im Internet erreichbaren Anwendungen mit vielen Nutzern.

Voraussetzung: Scope der Benutzersession

Bei der Analyse der Schwachstelle in unserer Testumgebung stellten wir fest, das die Session nicht nur an Realm des Nutzers, sondern auch an den client Scope beschränkt sind. Das wird im folgenden Screenshot gezeigt, wo ein Nutzer zwei separate Sessions, jede mit jeweils einem Client (account-console und test) hat. Für Angreifer ist das eher ungünstig, die Session des Opfers sollte idealerweise Zugriff auf mehrere “clients” erlauben. Angreifer können dadurch auch im Kontext des Opfers auf diese Clients-Zugreifen.

Je nachdem, wie die Applikation und OAuth verwendet wird kann es durchaus vorkommen, dass ein Nutzer mit derselben Session auf mehrere OAuth-Clients zugreift.

Attached Keycloak user session

Voraussetzung: Kenntnis der SessionID des Opfers

Diese Voraussetzung klingt recht schwierig. Bei der SessionID handelt es sich um eine UUIDv4 die sich nicht brute-forcen lässt. In einem realen Angriffsszenario benötigen wir daher einen anderen Weg um an diese ID zu kommen.

Der Einfachheit halber nehmen wir in unserem Beispiel an, dass der Angreifer sein Opfer dazu bringen kann eine von ihm kontrollierte Seite per Keycloak anzumelden. Denkbar wäre auch eine Cross Site Scripting (XSS) Schwachstelle in einer Anwendung, da sich die SessionID auch beispielsweise aus einem JWT Token auslesen lässt.

Beispiel OAuth Login

Nach einer erfolgreichen Authentifizierung benötigt der Angreifer einen zusätzlichen Schritt um sicherzustellen, dass sich das Opfer an einem weiteren Client, beispielsweise die Keycloak “Admin Console” (admin/master/console) aufruft und sich dort ebenfalls authentifiziert. Da das Opfer bereits per Keycloak angemeldet ist ist und die für einen “interaktionslosen” OAuth Flow benötigten Cookies mitsendet wird der security-admin-console OAuth Client automatisch zur Session hinzugefügt.

Die Angreifer können sich anschließend per CVE-2023-0264 mit ihrem niedrig privilegierten Keycloak-Nutzer an die administrative Session anfügen. Dies wurde im folgenden Video visualisiert. In einem realen Angriffsszenario muss man ein wenig kreativer sein um das Opfer zum Aufruf einer URL zu bewegen.

  • Der obere Browserfenster zeigt die aktuelle Session des Opfer-Kontos in Keycloak.
  • Das Opfer (unteres Browserfenster) meldet sich per OAuth auf der von den Angreifern kontrollierten Seite (Keycloak Client test) an. Danach erfolgt ein Zugriff auf die Admin-Konsole wodurch der security-admin-console Client zur aktuellen Session unseres Opfers hinzugefügt wird.

Anmeldung und Session-Binding

Die Benutzersession (aus Sicht von Keycloak) beinhaltet Final zwei Clients:

Keycloak Benutzersession mit zwei Clients

Der Ablauf des vorherigen Videos ist im folgenden Diagram illustriert:

Im Video gezeigter Ablauf des Angriffs

Ist man kein Red Teamer und benötigt beispielsweise im Rahmen eines regulären Penetrationstests oder Bug Bounty Programms ein Proof of Concept ist die Verifikation der Schwachstelle deutlich einfacher:

  1. Registrierung von zwei Benutzerkonten
  2. Anmeldung mit Account 1 um einen access_tokenzu erhalten.
  3. Auslesen des session_state Claims mit dem im vorherigen Schritt erhaltenen Token
  4. Ausnutzen der Schwachstelle mit Account 2

Das folgende JSON Objekt zeigt den JWT Payload eines typischen Keycloak Accesstokens den man im “Schritt 2” erhält. Die hervorgehobene Zeile ist der session_state, welche für den Keycloak access_token benötigt wird.

{
    "exp":1681401821,
    "iat":1681401761,
    "auth_time":1681401761,
    "jti":"524b3553-ee66-4cf6-b956-03027a308e44",
    "iss":"http://test.keycloak.local:8060/realms/master",
    "sub":"556da7c4-aaf0-43a9-9846-72ded642a777",
    "typ":"Bearer","azp":"security-admin-console",
    "nonce":"ae3b9770-e1ec-42e4-8f3f-1296d94d8b15",
    "session_state":"81dfb556-7a1a-440d-a59e-58b7dd2c0f90",
    "acr":"1",
    "allowed-origins":["http://test.keycloak.local:8060"],
    "scope":"openid profile email",
    "sid":"81dfb556-7a1a-440d-a59e-58b7dd2c0f90",
    "email_verified":false,
    "preferred_username":"victim"
}

Die session_state UUID eines Benutzers kann auch anderweitig kompromittiert werden. Keycloak setzt ein AUTH_SESSION_ID_LEGACY Cookie welches ebenfalls die aktuelle Client-Session enthält (siehe nächster Screenshot).

A cookie with the users client user session is set.

Rechteerweiterung

Kommen wir nun zum (recht einfachen) Ausnutzen dieser Schwachstelle. Wir zeigen wir Angreifer diese Schwachstelle ausnutzen können um die sich selbst an die Session eines anderen Nutzers anzuhängen.

In unserer Testumgebung lautet die URL des Keycloak Account Managements wie folgt:

Für unser Testszenario benötigen wir zunächst eine administrative Session die wir kompromittieren können. Dazu melden wir uns an der Admin-Console mit dem Account mogwailabs_adminan. Durch die Anmeldung fügt Keycloak den OAuth Client security-admin-consolezu unserer aktuellen Session “af52ab74-d534-4d58-b3ed-f89d7a73a22b” hinzu.

(In einem realen Angriffszenario würden Angreifer vermutlich einen anderen Weg finden um an den session_state des Opfers zu kommen, hierfür würde beispielsweise eine Cross-Site Scripting (XSS) Schwachstelle genügen)

Wir haben Zugriff auf den session_state eines administrativen Kontos

Mit der Session State UUID des Opfers können wir nun die eigentliche Schwachstelle ausnutzen indem wir einen entsprechenden code an Keycloak senden. Dazu melden wir uns mit dem vom Angreifer kontrollierten Account an und tauschen den zweiten Teil des code, wie bei der Patchanalyse beschrieben, aus. Die jeweiligen Schritte sind im folgenden Video und Ablaufdiagram illustriert.

Login into Keycloak with low privileged account

Exploit-Video für CVE-2023-0264.

Das folgende Ablaufdiagram wurde um die fehlenden Exploiting-Schritte erweitert. Der session_state des Opfers, den wir in den vorherigen Anmeldeschritten erhalten haben im vorletzten Schritt des Diagramms verwendet wird.

Ablaufdiagram zu CVE-2023-0264

Nachdem wir erfolgreich den Administrator impersoniert und einen gültigen Accesstoken erhalten haben können wir mit diesem auf die Keycloak Adminkonsole (oder jeder andere Client für den wir Zugriffsberechtigungen besitzen) zugreifen.

Abschließende Anmerkungen

Wir gingen, basierend auf der CVSS Bewertung sowie einer ersten Patchanalyse, zunächst von einer schwerwiegenden Schwachstelle aus. Das war auch der Grund warum wir diese überhaupt intensiver analysiert haben.

Es stellte sich aber heraus das die praktische Ausnutzung dieser Schwachstelle in einem realen Angriffsszenario recht schwierig ist. Hierfür müssen Angreifer Zugriff auf den Session-State eines anderen Nutzers erhalten, was beispielsweise das Ausnutzen einer weiteren Schwachstelle erfordert. Dennoch, die Möglichkeit eine administrativen Zugang zu einer Keycloak Instanz zu kompromittieren kann den notwendigen Aufwand für Angreifer rechtfertigen, da die Konsequenzen für eine Organisation verherend sein können.

Penetrationstester und Bugbounty Hunter werden vielleicht enttäuscht sein, da sich diese Schwachstelle nicht einfach in ein PoC umwandeln können um damit beispielsweise die Konten von Administratoren zu kompromittieren. Es handelts sich aber dennoch um eine schwere Schwachstelle, die sich gut in einem Report macht (oder einen kleine Bounty gibt).

Keycloak ist bei Schwachstellen durch GitHub Security Advisories transparent, uns viel aber auf das ihr Release Blog nur selten security-relevante Updates erwähnt. Einige Administratoren könnten daher eventuell wichtige Updates für ihre Keycloak Instanzen übersehen.


Danke an Cécile Brasseur bei Unsplash für das Titelbild.