Analog zum bequemen und allseits beliebten Single-Sign-On in einem on-premise Active Directory, ermöglicht Microsoft für Azure bzw. für Anwendungen in Azure einen ähnlich komfortablen Mechanismus. Ein Anwender, der auf einem Azure gejointem bzw. einem “hybrid joint Device” angemeldet ist, kann native Cloud-Anwendungen und auch die entsprechenden Webseiten ohne weitere Anmeldung verwenden. Letzteres natürlich nur unter der Voraussetzung er nutzt den richtigen Browser bzw. hat die entsprechende Erweiterungen installiert.
Den dafür verwendeten Mechanismus hat Dirk-jan Mollema auf seinem Blog bereits hier und hier sehr detailliert beschrieben. Weiterhin hat er diverse Anmeldemechanismen in seiner Suite roadtools implementiert. Um die Vorgänge bei der Anmeldung besser zu verstehen, habe ich einen Teil der Authentifizierung von Python nach C# portiert. Das Projekt ist hier zu finden.
Um SSO in Windows 10 zu ermöglichen, nutzt Microsoft eine angepasste Version von OAuth2. Bei der ersten Anmeldung mittels Azure AD erhält der Client für den Benutzer ein Primary Refresh Token (PRT), ein SessionKey und ein Identity Token. Das PRT ist vergleichbar mit einem Ticket Granting Ticket (TGT) aus der Kerberos Welt. Es ermöglicht weitere Refresh und Access Tokens bei Azure AD anzufragen. Technisch ähnelt es allerdings sehr dem Refresh Token aus der OAuth2 Spezifikation. Der größte Unterschied ist, dass während ein Refresh Token in OAuth2 nur für einen Service verwendet werden kann, kann ein PRT für alle Services verwendet werden. Der Session Key wird vom TPM verschlüsselt und landet zusammen mit dem PRT im Cloudap Plugin der LSASS auf dem System. Der Schlüssel für den Session Key bleibt im TPM. Die entsprechenden Apps oder Browser können über Schnittstellen die notwendigen Tokens anfragen und im Anschluss verwenden.
Um zu sehen, ob ein Client Teil eines Azure Active Directory ist, kann folgender Befehl verwendet werden:
dsregcmd /status
Das Ergebnis sieht dann ungefähr so aus:
Wenn der Benutzer den Browser Edge öffnet und beispielsweise auf https://portal.azure.com geht, ist er wie von Zauberhand an der Webseite angemeldet. Mittels Interception Proxy kann der Datenverkehr mitgelesen werden.
Für die Anmeldung wird der Browser zunächst auf die folgende URL umgeleitet:
https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?client_id=c44b4083-3bb0-49c1-b47d-974e53cbdf3c
&response_type=code id_token
&scope=https://management.core.windows.net//user_impersonation openid email profile
&state=OpenIdConnect.AuthenticationProperties=<TRUNK>
&response_mode=form_post
&nonce=<TRUNK>
&redirect_uri=https://portal.azure.com/signin/index/&site_id=501430
&client-request-id=*Redacted*
&x-client-SKU=ID_NET45
&x-client-ver=5.3.0.0
In der Anfrage sendet der Client ein Cookie mit dem Namen x-ms-RefreshTokenCredential
mit. Das Cookie enthält ein JSON Web Token, dass auch als PRT-Cookie bezeichnet wird. Decodiert hat es folgenden Inhalt:
{"alg":"HS256", "ctx":"7CFzFq6y9aRk4**************"}.
{"refresh_token":"0.AVwAK179j6yifk-C-vVmS*Redacted*", "is_primary":"true", "iat":"1619014117"}.
HVVJ7_dlWf5jCf1UM1Yte_J7lY4wme_VRQnOjOqae74
Der Server antwortet mit einem 302 Code und folgender Ziel Adresse:
https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?client_id=c44b4083-3bb0-49c1-b47d-974e53cbdf3c
&response_type=code id_token
&scope=https://management.core.windows.net//user_impersonation openid email profile
&state=OpenIdConnect.AuthenticationProperties=<TRUNK>
&response_mode=form_post
&nonce=<TRUNK>
&redirect_uri=https://portal.azure.com/signin/index/&site_id=501430&client-request-id=*Redacted*
&x-client-SKU=ID_NET45
&x-client-ver=5.3.0.0
&sso_nonce=AwABAAAAAAACAOz_BAD0_w26uWsfCrMZ4lc-meDT_l5UETr-geHe6PQ6yGWckW26N80_JRFHULaGwDjREJGMjPgDXIWcjc5jwUCAaGujvNsgAA
&mscrid=37cb2f5a-a7e3-4b62-9908-d8a91c008a99
Letztendlich wird die gleiche Seite noch einmal aufgerufen, allerdings dieses Mal mit einer zusätzlichen Nonce. Diese Nonce wird seit ca. Oktober 2020 benötigt. Ohne sie geht, dass später gezeigt Pass-The-PRT nicht. Also nicht wundern, falls ihr alte Demos nicht nachmachen könnt.
Beim Aufruf der gesendeten Ziel URL wird wieder das Cookie x-ms-RefreshTokenCredential
mitgesendet, diesmal inklusive der Nonce:
{"alg":"HS256", "ctx":"VTcByH4VY+bU6OYf9************"}.
{"refresh_token":"0.AVwAK179j6yifk-C-vVm*Redacted*", "is_primary":"true", "request_nonce":"AwABAAAAAAACAOz_BAD0_w26uWsfCrMZ4lc-meDT_l5UETr-geHe6PQ6yGWckW26N80_JRFHULaGwDjREJGMjPgDXIWcjc5jwUCAaGujvNsgAA"}.
ezH5sVll8SDytianOSw9MsqoRZI6dY-xdGAtiC1Ewc4
Als Antwort auf den Aufruf bekommt der Client einen Authorization Code zurück, der in einer weiteren Anfrage an https://portal.azure.com/signin/index/
gesendet wird und damit ist der Benutzer angemeldet. Das ganze Prozedere entspricht dann ungefähr dem OAuth2 Authorization Code Flow.
Wie in den Blogs von Dirk-Jan Moleman beschrieben, wird das PRT-Cookie von Windows dem Browser und anderen Anwendungen über diverse Mechanismen bereitgestellt und ermöglicht damit das SSO in Azure. Weiterhin wird im Blog beschrieben, wie dieses Cookie erzeugt wird.
Für ein PRT-Cookie werden folgende Zutaten benötigt:
An die Nonce kommen wir leicht, in dem wir einfach eine entsprechende Anfrage bei Azure stellen. Der Context kann theoretisch selbst gewählt werden, er fließt allerdings in die Berechnung des Derived Key ein. Für die Berechnung des Derived Key benötigen wir den Session Key, der (normalerweise) durch das TPM geschützt wird. Wenn wir den Session Key hätten, könnten wir aus dem selbst gewählten Context, dem Session Key, dem Wort “AzureAD-SecureConversation” und der entsprechenden Key Derivation Function den Derived Key berechnen. Eine Python Funktion zum Erzeugen des Derived Key kann hier eingesehen werden.
Um an das PRT und den Derived Key zu kommen, müssen wir zunächst in die Tiefen von Windows abtauchen. Zum Glück hat Benjamin Delpy mit Mimikatz hier bereits die notwendigen arbeiten erledigt. Mimikatz ermöglicht es direkt den Inhalt des CloudAp Plugins einzusehen.
privilege::debug
sekurlsa::cloudap
Als Ergebnis auf die beiden Befehle, bekommen wir ungefähr folgendes zurück:
In der Ausgabe ist das PRT und ein “ProofOfPossessionKey” zu sehen. Das PRT können wir so verwenden, wie es hier angezeigt wird. Der “ProofOfPossessionKey” ist der verschlüsselte Session Key, der mit dem PRT auf dem System gespeichert wurde und dessen Schlüssel im TPM liegt. Damit kommen wir nicht an den Session Key. Glücklicher weise nimmt uns das Betriebssystem die Erzeugung eines Derived Keys ab, wenn man es nur freundlich fragt. Auch dafür hat Benjamin Delpy bereits den notwendigen Funktionen geschrieben.
token::elevate
dpapi::cloudapkd /keyvalue:<ProofOfPossesionKey> /unprotect
Der Aufruf resultiert in folgendem Ergebnis:
Mit dem Derived Key, dem Context einer Nonce und dem PRT kann nun ein PRT-Cookie erzeugt werden. Wie oben beschrieben, handelt es sich bei dem PRT-Cookie um ein JSON Web Token. Das JWT ist im folgenden nochmal dargestellt:
{"alg":"HS256", "ctx":"VTcByH4VY+bU6OYf9************"}.
{"refresh_token":"0.AVwAK179j6yifk-C-vVm*Redacted*", "is_primary":"true", "request_nonce":"AwABAAAAAAACAOz_BAD0_w26uWsfCrMZ4lc-meDT_l5UETr-geHe6PQ6yGWckW26N80_JRFHULaGwDjREJGMjPgDXIWcjc5jwUCAaGujvNsgAA"}.
ezH5sVll8SDytianOSw9MsqoRZI6dY-xdGAtiC1Ewc4
Der Context wird in den Header als Wert für ctx eingetragen. Das PRT entspricht dem refresh_token und die Nonce, nun ja… dem Wert nonce. Im Anschluss wird das JWT mit dem Derived Key signiert und fertig ist das PRT-Cookie.
Mit dem PRT-Cookie können nun zwei Dinge getan werden. Erstens, es kann in einem Browser für die Anmeldung an beliebigen Microsoft Portalen, die AzureAD zur Anmeldung nutzen, verwendet werden. Zweitens es kann für die Anfrage für Access Token für andere Anwendungen/APIs genutzt werden.
Für die Anmeldung an den Microsoft Portalen in Chrome empfiehlt sich folgendes Vorgehen:
Für das Eintauschen eines PRT-Cookie gegen ein Access Token ist weitere Tool Unterstützung notwendig, dazu später mehr.
Um ein PRT-Cookie in der Praxis ausstellen zu lassen, gibt es derzeit zwei Möglichkeiten.
Die erste Möglichkeit ist die Verwendung von roadtools und Roadtoken. Beides sind Anwendungen von Dirk-Jan Moleman. Roadtools ist eine Anwendung zum Auslesen von Azure Active Directory Daten. Im Kontext von diesem Blog wird lediglich seine Fähigkeit zum Anfragen einer Nonce bei Azure benötigt.
Im Anschluss kann das Tool Roadtoken im Kontext des Benutzers, den ihr in Azure übernehmen wollt, ausgeführt werden. Letztendlich nutzt Roadtoken den gleichen Weg um ein PRT-Cookie zu erzeugen, wie es der Browser tut. Einfach indem es das Betriebssystem danach fragt.
Das PRT-Cookie ist im Feld data enthalten.
Lantern ist ein Tool zum Anfragen von Access Token, Refresh Tokens und PRT-Cookies. Zum Erzeugen eines PRT-Cookies werden die Daten aus Mimikatz benötigt.
Ein PRT-Cookie kann nicht nur zum Anmelden in Browser verwendet werden. Es ermöglicht auch weitere Refresh und Access Tokens für anderen Anwendungen anzufragen.
Für die Anfrage nach Azure Tokens können wieder beide Tools verwendet werden.
roadtools erlaubt das Verwenden eines PRT-Cookies beispielsweise aus Roadtoken für die Anmeldung an Azure.
Lantern kann ebenfalls Access und Refresh Tokens aus einem PRT-Cookie erzeugen.
Das Access Token kann jetzt beispielsweise mit dem Azure Powershell Modul AzureAD verwendet werden.
Der wohl effizienteste Schutz gegen Pass-The-PRT ist wohl nur eine sichere Gerätekonfiguration. Da das PRT laut Dokumentation die MFA Claims innehat, würde hier MFA kein zusätzlicher Schutz bieten. Wenn der Verdacht besteht, dass ein Benutzer oder ein Gerät kompromittiert wurde, sollte das Gerät in Azure deaktiviert werden. Bei meinen Tests war es auch nicht notwendig, die Refresh Tokens extra zu löschen. Wenn das Gerät in Azure deaktiviert wurde, konnte ich die Refresh Tokens nicht verwenden, um neue Access Tokens anzufragen. Auf die Access Tokens hat dies kein Einfluss. Diese bleiben weiterhin gültig.
Wie immer gilt falls ihr Fragen, Anmerkungen oder Verbesserungsvorschläge zu dem Artikel habt, schreibt eine Mail an info@hackmich.net.